/* @internal */ namespace ts { const ambientModuleSymbolRegex = /^".+"$/; const anon = "(anonymous)" as __String & string; let nextSymbolId = 1; let nextNodeId = 1; let nextMergeId = 1; let nextFlowId = 1; const enum IterationUse { AllowsSyncIterablesFlag = 1 << 0, AllowsAsyncIterablesFlag = 1 << 1, AllowsStringInputFlag = 1 << 2, ForOfFlag = 1 << 3, YieldStarFlag = 1 << 4, SpreadFlag = 1 << 5, DestructuringFlag = 1 << 6, PossiblyOutOfBounds = 1 << 7, // Spread, Destructuring, Array element assignment Element = AllowsSyncIterablesFlag, Spread = AllowsSyncIterablesFlag | SpreadFlag, Destructuring = AllowsSyncIterablesFlag | DestructuringFlag, ForOf = AllowsSyncIterablesFlag | AllowsStringInputFlag | ForOfFlag, ForAwaitOf = AllowsSyncIterablesFlag | AllowsAsyncIterablesFlag | AllowsStringInputFlag | ForOfFlag, YieldStar = AllowsSyncIterablesFlag | YieldStarFlag, AsyncYieldStar = AllowsSyncIterablesFlag | AllowsAsyncIterablesFlag | YieldStarFlag, GeneratorReturnType = AllowsSyncIterablesFlag, AsyncGeneratorReturnType = AllowsAsyncIterablesFlag, } const enum IterationTypeKind { Yield, Return, Next, } interface IterationTypesResolver { iterableCacheKey: "iterationTypesOfAsyncIterable" | "iterationTypesOfIterable"; iteratorCacheKey: "iterationTypesOfAsyncIterator" | "iterationTypesOfIterator"; iteratorSymbolName: "asyncIterator" | "iterator"; getGlobalIteratorType: (reportErrors: boolean) => GenericType; getGlobalIterableType: (reportErrors: boolean) => GenericType; getGlobalIterableIteratorType: (reportErrors: boolean) => GenericType; getGlobalGeneratorType: (reportErrors: boolean) => GenericType; resolveIterationType: (type: Type, errorNode: Node | undefined) => Type | undefined; mustHaveANextMethodDiagnostic: DiagnosticMessage; mustBeAMethodDiagnostic: DiagnosticMessage; mustHaveAValueDiagnostic: DiagnosticMessage; } const enum WideningKind { Normal, FunctionReturn, GeneratorNext, GeneratorYield, } const enum TypeFacts { None = 0, TypeofEQString = 1 << 0, // typeof x === "string" TypeofEQNumber = 1 << 1, // typeof x === "number" TypeofEQBigInt = 1 << 2, // typeof x === "bigint" TypeofEQBoolean = 1 << 3, // typeof x === "boolean" TypeofEQSymbol = 1 << 4, // typeof x === "symbol" TypeofEQObject = 1 << 5, // typeof x === "object" TypeofEQFunction = 1 << 6, // typeof x === "function" TypeofEQHostObject = 1 << 7, // typeof x === "xxx" TypeofNEString = 1 << 8, // typeof x !== "string" TypeofNENumber = 1 << 9, // typeof x !== "number" TypeofNEBigInt = 1 << 10, // typeof x !== "bigint" TypeofNEBoolean = 1 << 11, // typeof x !== "boolean" TypeofNESymbol = 1 << 12, // typeof x !== "symbol" TypeofNEObject = 1 << 13, // typeof x !== "object" TypeofNEFunction = 1 << 14, // typeof x !== "function" TypeofNEHostObject = 1 << 15, // typeof x !== "xxx" EQUndefined = 1 << 16, // x === undefined EQNull = 1 << 17, // x === null EQUndefinedOrNull = 1 << 18, // x === undefined / x === null NEUndefined = 1 << 19, // x !== undefined NENull = 1 << 20, // x !== null NEUndefinedOrNull = 1 << 21, // x != undefined / x != null Truthy = 1 << 22, // x Falsy = 1 << 23, // !x All = (1 << 24) - 1, // The following members encode facts about particular kinds of types for use in the getTypeFacts function. // The presence of a particular fact means that the given test is true for some (and possibly all) values // of that kind of type. BaseStringStrictFacts = TypeofEQString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, BaseStringFacts = BaseStringStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, StringStrictFacts = BaseStringStrictFacts | Truthy | Falsy, StringFacts = BaseStringFacts | Truthy, EmptyStringStrictFacts = BaseStringStrictFacts | Falsy, EmptyStringFacts = BaseStringFacts, NonEmptyStringStrictFacts = BaseStringStrictFacts | Truthy, NonEmptyStringFacts = BaseStringFacts | Truthy, BaseNumberStrictFacts = TypeofEQNumber | TypeofNEString | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, BaseNumberFacts = BaseNumberStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, NumberStrictFacts = BaseNumberStrictFacts | Truthy | Falsy, NumberFacts = BaseNumberFacts | Truthy, ZeroNumberStrictFacts = BaseNumberStrictFacts | Falsy, ZeroNumberFacts = BaseNumberFacts, NonZeroNumberStrictFacts = BaseNumberStrictFacts | Truthy, NonZeroNumberFacts = BaseNumberFacts | Truthy, BaseBigIntStrictFacts = TypeofEQBigInt | TypeofNEString | TypeofNENumber | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, BaseBigIntFacts = BaseBigIntStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, BigIntStrictFacts = BaseBigIntStrictFacts | Truthy | Falsy, BigIntFacts = BaseBigIntFacts | Truthy, ZeroBigIntStrictFacts = BaseBigIntStrictFacts | Falsy, ZeroBigIntFacts = BaseBigIntFacts, NonZeroBigIntStrictFacts = BaseBigIntStrictFacts | Truthy, NonZeroBigIntFacts = BaseBigIntFacts | Truthy, BaseBooleanStrictFacts = TypeofEQBoolean | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull, BaseBooleanFacts = BaseBooleanStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, BooleanStrictFacts = BaseBooleanStrictFacts | Truthy | Falsy, BooleanFacts = BaseBooleanFacts | Truthy, FalseStrictFacts = BaseBooleanStrictFacts | Falsy, FalseFacts = BaseBooleanFacts, TrueStrictFacts = BaseBooleanStrictFacts | Truthy, TrueFacts = BaseBooleanFacts | Truthy, SymbolStrictFacts = TypeofEQSymbol | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, SymbolFacts = SymbolStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, ObjectStrictFacts = TypeofEQObject | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | NEUndefined | NENull | NEUndefinedOrNull | Truthy, ObjectFacts = ObjectStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, FunctionStrictFacts = TypeofEQFunction | TypeofEQHostObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | NEUndefined | NENull | NEUndefinedOrNull | Truthy, FunctionFacts = FunctionStrictFacts | EQUndefined | EQNull | EQUndefinedOrNull | Falsy, UndefinedFacts = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | TypeofNEHostObject | EQUndefined | EQUndefinedOrNull | NENull | Falsy, NullFacts = TypeofEQObject | TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEFunction | TypeofNEHostObject | EQNull | EQUndefinedOrNull | NEUndefined | Falsy, EmptyObjectStrictFacts = All & ~(EQUndefined | EQNull | EQUndefinedOrNull), AllTypeofNE = TypeofNEString | TypeofNENumber | TypeofNEBigInt | TypeofNEBoolean | TypeofNESymbol | TypeofNEObject | TypeofNEFunction | NEUndefined, EmptyObjectFacts = All, } const typeofEQFacts: ReadonlyESMap = new Map(getEntries({ string: TypeFacts.TypeofEQString, number: TypeFacts.TypeofEQNumber, bigint: TypeFacts.TypeofEQBigInt, boolean: TypeFacts.TypeofEQBoolean, symbol: TypeFacts.TypeofEQSymbol, undefined: TypeFacts.EQUndefined, object: TypeFacts.TypeofEQObject, function: TypeFacts.TypeofEQFunction })); const typeofNEFacts: ReadonlyESMap = new Map(getEntries({ 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, EnumTagType, ResolvedTypeArguments, ResolvedBaseTypes, } const enum CheckMode { Normal = 0, // Normal type checking Contextual = 1 << 0, // Explicitly assigned contextual type, therefore not cacheable Inferential = 1 << 1, // Inferential typing SkipContextSensitive = 1 << 2, // Skip context sensitive function expressions SkipGenericFunctions = 1 << 3, // Skip single signature generic functions IsForSignatureHelp = 1 << 4, // Call resolution for purposes of signature help } const enum AccessFlags { None = 0, NoIndexSignatures = 1 << 0, Writing = 1 << 1, CacheSymbol = 1 << 2, NoTupleBoundsCheck = 1 << 3, ExpressionPosition = 1 << 4, } const enum SignatureCheckMode { BivariantCallback = 1 << 0, StrictCallback = 1 << 1, IgnoreReturnTypes = 1 << 2, StrictArity = 1 << 3, Callback = BivariantCallback | StrictCallback, } const enum IntersectionState { None = 0, Source = 1 << 0, Target = 1 << 1, PropertyCheck = 1 << 2, InPropertyCheck = 1 << 3, } const enum MappedTypeModifiers { IncludeReadonly = 1 << 0, ExcludeReadonly = 1 << 1, IncludeOptional = 1 << 2, ExcludeOptional = 1 << 3, } const enum ExpandingFlags { None = 0, Source = 1, Target = 1 << 1, Both = Source | Target, } const enum MembersOrExportsResolutionKind { resolvedExports = "resolvedExports", resolvedMembers = "resolvedMembers" } const enum UnusedKind { Local, Parameter, } /** @param containingNode Node to check for parse error */ type AddUnusedDiagnostic = (containingNode: Node, type: UnusedKind, diagnostic: DiagnosticWithLocation) => void; const isNotOverloadAndNotAccessor = and(isNotOverload, isNotAccessor); const enum DeclarationMeaning { GetAccessor = 1, SetAccessor = 2, PropertyAssignment = 4, Method = 8, GetOrSetAccessor = GetAccessor | SetAccessor, PropertyAssignmentOrMethod = PropertyAssignment | Method, } const enum DeclarationSpaces { None = 0, ExportValue = 1 << 0, ExportType = 1 << 1, ExportNamespace = 1 << 2, } const enum MinArgumentCountFlags { None = 0, StrongArityForUntypedJS = 1 << 0, VoidIsNonOptional = 1 << 1, } const enum IntrinsicTypeKind { Uppercase, Lowercase, Capitalize, Uncapitalize } const intrinsicTypeKinds: ReadonlyESMap = new Map(getEntries({ Uppercase: IntrinsicTypeKind.Uppercase, Lowercase: IntrinsicTypeKind.Lowercase, Capitalize: IntrinsicTypeKind.Capitalize, Uncapitalize: IntrinsicTypeKind.Uncapitalize })); function SymbolLinks(this: SymbolLinks) { } function NodeLinks(this: NodeLinks) { this.flags = 0; } export function getNodeId(node: Node): number { if (!node.id) { node.id = nextNodeId; nextNodeId++; } return node.id; } export function getSymbolId(symbol: Symbol): SymbolId { if (!symbol.id) { symbol.id = nextSymbolId; nextSymbolId++; } return symbol.id; } export function isInstantiatedModule(node: ModuleDeclaration, preserveConstEnums: boolean) { const moduleState = getModuleInstanceState(node); return moduleState === ModuleInstanceState.Instantiated || (preserveConstEnums && moduleState === ModuleInstanceState.ConstEnumOnly); } export function createTypeChecker(host: TypeCheckerHost, produceDiagnostics: boolean): TypeChecker { const getPackagesSet = memoize(() => { const set = new Set(); host.getSourceFiles().forEach(sf => { if (!sf.resolvedModules) return; forEachEntry(sf.resolvedModules, r => { if (r && r.packageId) set.add(r.packageId.name); }); }); return set; }); // 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). let cancellationToken: CancellationToken | undefined; let requestedExternalEmitHelpers: ExternalEmitHelpers; let externalHelpersModule: Symbol; const Symbol = objectAllocator.getSymbolConstructor(); const Type = objectAllocator.getTypeConstructor(); const Signature = objectAllocator.getSignatureConstructor(); let typeCount = 0; let symbolCount = 0; let enumCount = 0; let totalInstantiationCount = 0; let instantiationCount = 0; let instantiationDepth = 0; let constraintDepth = 0; let currentNode: Node | undefined; const typeCatalog: Type[] = []; // NB: id is index + 1 const emptySymbols = createSymbolTable(); const arrayVariances = [VarianceFlags.Covariant]; const compilerOptions = host.getCompilerOptions(); const languageVersion = getEmitScriptTarget(compilerOptions); const moduleKind = getEmitModuleKind(compilerOptions); const allowSyntheticDefaultImports = getAllowSyntheticDefaultImports(compilerOptions); const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks"); const strictFunctionTypes = getStrictOptionValue(compilerOptions, "strictFunctionTypes"); const strictBindCallApply = getStrictOptionValue(compilerOptions, "strictBindCallApply"); const strictPropertyInitialization = getStrictOptionValue(compilerOptions, "strictPropertyInitialization"); const noImplicitAny = getStrictOptionValue(compilerOptions, "noImplicitAny"); const noImplicitThis = getStrictOptionValue(compilerOptions, "noImplicitThis"); const keyofStringsOnly = !!compilerOptions.keyofStringsOnly; const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : ObjectFlags.FreshLiteral; const emitResolver = createResolver(); const nodeBuilder = createNodeBuilder(); const globals = createSymbolTable(); const undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined" as __String); undefinedSymbol.declarations = []; const globalThisSymbol = createSymbol(SymbolFlags.Module, "globalThis" as __String, CheckFlags.Readonly); globalThisSymbol.exports = globals; globalThisSymbol.declarations = []; globals.set(globalThisSymbol.escapedName, globalThisSymbol); const argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String); const requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String); /** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */ let apparentArgumentCount: number | undefined; // 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: () => sum(host.getSourceFiles(), "nodeCount"), getIdentifierCount: () => sum(host.getSourceFiles(), "identifierCount"), getSymbolCount: () => sum(host.getSourceFiles(), "symbolCount") + symbolCount, getTypeCatalog: () => typeCatalog, 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, getDiagnostics, getGlobalDiagnostics, getRecursionIdentity, getTypeOfSymbolAtLocation: (symbol, locationIn) => { const location = getParseTreeNode(locationIn); return location ? getTypeOfSymbolAtLocation(symbol, location) : errorType; }, 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."); 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, getSignaturesOfType, getIndexTypeOfType, getBaseTypes, getBaseTypeOfLiteralType, getWidenedType, getTypeFromTypeNode: nodeIn => { const node = getParseTreeNode(nodeIn, isTypeNode); return node ? getTypeFromTypeNode(node) : errorType; }, getParameterType: getTypeAtPosition, getPromisedTypeOfPromise, getAwaitedType: type => getAwaitedType(type), getReturnTypeOfSignature, isNullableType, getNullableType, getNonNullableType, getNonOptionalType: removeOptionalTypeMarker, getTypeArguments, typeToTypeNode: nodeBuilder.typeToTypeNode, indexInfoToIndexSignatureDeclaration: nodeBuilder.indexInfoToIndexSignatureDeclaration, signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration, symbolToEntityName: nodeBuilder.symbolToEntityName, symbolToExpression: nodeBuilder.symbolToExpression, 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; }, 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) => { return signatureToString(signature, getParseTreeNode(enclosingDeclaration), flags, kind, writer); }, writeType: (type, enclosingDeclaration, flags, writer) => { return typeToString(type, getParseTreeNode(enclosingDeclaration), flags, writer); }, 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; } const containingCall = findAncestor(node, isCallLikeExpression); const containingCallResolvedSignature = containingCall && getNodeLinks(containingCall).resolvedSignature; if (contextFlags! & ContextFlags.Completions && containingCall) { let toMarkSkip = node as Node; do { getNodeLinks(toMarkSkip).skipDirectInference = true; toMarkSkip = toMarkSkip.parent; } while (toMarkSkip && toMarkSkip !== containingCall); getNodeLinks(containingCall).resolvedSignature = undefined; } const result = getContextualType(node, contextFlags); if (contextFlags! & ContextFlags.Completions && containingCall) { let toMarkSkip = node as Node; do { getNodeLinks(toMarkSkip).skipDirectInference = undefined; toMarkSkip = toMarkSkip.parent; } while (toMarkSkip && toMarkSkip !== containingCall); getNodeLinks(containingCall).resolvedSignature = containingCallResolvedSignature; } return result; }, getContextualTypeForObjectLiteralElement: nodeIn => { const node = getParseTreeNode(nodeIn, isObjectLiteralElementLike); return node ? getContextualTypeForObjectLiteralElement(node) : 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); }, isContextSensitive, getFullyQualifiedName, getResolvedSignature: (node, candidatesOutArray, argumentCount) => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.Normal), getResolvedSignatureForSignatureHelp: (node, candidatesOutArray, argumentCount) => getResolvedSignatureWorker(node, candidatesOutArray, argumentCount, CheckMode.IsForSignatureHelp), getExpandedParameters, hasEffectiveRestParameter, 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, getExportsOfModule: getExportsOfModuleAsArray, getExportsAndPropertiesOfModule, getSymbolWalker: createGetSymbolWalker( getRestTypeOfSignature, getTypePredicateOfSignature, getReturnTypeOfSignature, getBaseTypes, resolveStructuredTypeMembers, getTypeOfSymbol, getResolvedSymbol, getIndexTypeOfStructuredType, 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), tryFindAmbientModuleWithoutAugmentations: moduleName => { // we deliberately exclude augmentations // since we are only interested in declarations of the module itself return tryFindAmbientModule(moduleName, /*withAugmentations*/ false); }, getApparentType, getUnionType, isTypeAssignableTo, createAnonymousType, createSignature, createSymbol, createIndexInfo, getAnyType: () => anyType, getStringType: () => stringType, getNumberType: () => numberType, 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, getOptionalType: () => optionalType, isSymbolAccessible, isArrayType, isTupleType, isArrayLikeType, isTypeInvalidDueToUnionDiscriminant, getAllPossiblePropertiesOfTypes, getSuggestedSymbolForNonexistentProperty, getSuggestionForNonexistentProperty, getSuggestedSymbolForNonexistentJSXAttribute, getSuggestedSymbolForNonexistentSymbol: (location, name, meaning) => getSuggestedSymbolForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning), getSuggestedSymbolForNonexistentModule, getSuggestionForNonexistentExport, 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, /*nameArg*/ undefined, /*isUse*/ false, excludeGlobals); }, getJsxNamespace: n => unescapeLeadingUnderscores(getJsxNamespace(n)), getAccessibleSymbolChain, getTypePredicateOfSignature, resolveExternalModuleName: moduleSpecifierIn => { const moduleSpecifier = getParseTreeNode(moduleSpecifierIn, isExpression); return moduleSpecifier && resolveExternalModuleName(moduleSpecifier, moduleSpecifier, /*ignoreErrors*/ true); }, resolveExternalModuleSymbol, tryGetThisTypeAt: (nodeIn, includeGlobalThis) => { const node = getParseTreeNode(nodeIn); return node && tryGetThisTypeAt(node, includeGlobalThis); }, 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 checkSourceFile(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, }; function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined { const node = getParseTreeNode(nodeIn, isCallLikeExpression); apparentArgumentCount = argumentCount; const res = node ? getResolvedSignature(node, candidatesOutArray, checkMode) : undefined; apparentArgumentCount = undefined; return res; } const tupleTypes = new Map(); const unionTypes = new Map(); const intersectionTypes = new Map(); const literalTypes = new Map(); const indexedAccessTypes = new Map(); const templateLiteralTypes = new Map(); const stringMappingTypes = new Map(); const substitutionTypes = new Map(); const evolvingArrayTypes: EvolvingArrayType[] = []; const undefinedProperties: SymbolTable = new Map(); const unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String); const resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving); const anyType = createIntrinsicType(TypeFlags.Any, "any"); const autoType = createIntrinsicType(TypeFlags.Any, "any"); const wildcardType = createIntrinsicType(TypeFlags.Any, "any"); const errorType = createIntrinsicType(TypeFlags.Any, "error"); const nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType); const intrinsicMarkerType = createIntrinsicType(TypeFlags.Any, "intrinsic"); const unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); const undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); const undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType); const optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined"); const nullType = createIntrinsicType(TypeFlags.Null, "null"); const nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType); const stringType = createIntrinsicType(TypeFlags.String, "string"); const numberType = createIntrinsicType(TypeFlags.Number, "number"); const bigintType = createIntrinsicType(TypeFlags.BigInt, "bigint"); const falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; const regularFalseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; const trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType; const 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; const booleanType = createBooleanType([regularFalseType, regularTrueType]); // Also mark all combinations of fresh/regular booleans as "Boolean" so they print as `boolean` instead of `true | false` // (The union is cached, so simply doing the marking here is sufficient) createBooleanType([regularFalseType, trueType]); createBooleanType([falseType, regularTrueType]); createBooleanType([falseType, trueType]); const esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); const voidType = createIntrinsicType(TypeFlags.Void, "void"); const neverType = createIntrinsicType(TypeFlags.Never, "never"); const silentNeverType = createIntrinsicType(TypeFlags.Never, "never"); const nonInferrableType = createIntrinsicType(TypeFlags.Never, "never", ObjectFlags.NonInferrableType); const implicitNeverType = createIntrinsicType(TypeFlags.Never, "never"); const unreachableNeverType = createIntrinsicType(TypeFlags.Never, "never"); const nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object"); const stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]); const keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType; const numberOrBigIntType = getUnionType([numberType, bigintType]); const templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType, nullType, undefinedType]) as UnionType; const restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(t) : t); const permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t); const emptyObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); const emptyJsxObjectType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); emptyJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes; const emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); emptyTypeLiteralSymbol.members = createSymbolTable(); const emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, undefined, undefined); const emptyGenericType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); emptyGenericType.instantiations = new Map(); const anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated // in getPropagatingFlagsOfTypes, and it is checked in inferFromTypes. anyFunctionType.objectFlags |= ObjectFlags.NonInferrableType; const noConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); const circularConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); const resolvingDefaultType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); const markerSuperType = createTypeParameter(); const markerSubType = createTypeParameter(); markerSubType.constraint = markerSuperType; const markerOtherType = createTypeParameter(); const noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<>", 0, anyType); const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true); const iterationTypesCache = new Map(); // cache for common IterationTypes instances const 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"); }, }; const anyIterationTypes = createIterationTypes(anyType, anyType, anyType); const anyIterationTypesExceptNext = createIterationTypes(anyType, anyType, unknownType); const defaultIterationTypes = createIterationTypes(neverType, anyType, undefinedType); // default iteration types for `Iterator`. const asyncIterationTypesResolver: IterationTypesResolver = { iterableCacheKey: "iterationTypesOfAsyncIterable", iteratorCacheKey: "iterationTypesOfAsyncIterator", iteratorSymbolName: "asyncIterator", getGlobalIteratorType: getGlobalAsyncIteratorType, getGlobalIterableType: getGlobalAsyncIterableType, getGlobalIterableIteratorType: getGlobalAsyncIterableIteratorType, getGlobalGeneratorType: getGlobalAsyncGeneratorType, resolveIterationType: getAwaitedType, 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, }; const syncIterationTypesResolver: IterationTypesResolver = { iterableCacheKey: "iterationTypesOfIterable", iteratorCacheKey: "iterationTypesOfIterator", iteratorSymbolName: "iterator", getGlobalIteratorType, getGlobalIterableType, getGlobalIterableIteratorType, getGlobalGeneratorType, 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: ESMap; } /** Key is "/path/to/a.ts|/path/to/b.ts". */ let amalgamatedDuplicates: ESMap | undefined; const reverseMappedCache = new Map(); let inInferTypeForHomomorphicMappedType = false; let 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. */ let patternAmbientModules: PatternAmbientModule[]; let patternAmbientModuleAugmentations: ESMap | undefined; let globalObjectType: ObjectType; let globalFunctionType: ObjectType; let globalCallableFunctionType: ObjectType; let globalNewableFunctionType: ObjectType; let globalArrayType: GenericType; let globalReadonlyArrayType: GenericType; let globalStringType: ObjectType; let globalNumberType: ObjectType; let globalBooleanType: ObjectType; let globalRegExpType: ObjectType; let globalThisType: GenericType; let anyArrayType: Type; let autoArrayType: Type; let anyReadonlyArrayType: Type; let 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 let deferredGlobalESSymbolConstructorSymbol: Symbol | undefined; let deferredGlobalESSymbolType: ObjectType; let deferredGlobalTypedPropertyDescriptorType: GenericType; let deferredGlobalPromiseType: GenericType; let deferredGlobalPromiseLikeType: GenericType; let deferredGlobalPromiseConstructorSymbol: Symbol | undefined; let deferredGlobalPromiseConstructorLikeType: ObjectType; let deferredGlobalIterableType: GenericType; let deferredGlobalIteratorType: GenericType; let deferredGlobalIterableIteratorType: GenericType; let deferredGlobalGeneratorType: GenericType; let deferredGlobalIteratorYieldResultType: GenericType; let deferredGlobalIteratorReturnResultType: GenericType; let deferredGlobalAsyncIterableType: GenericType; let deferredGlobalAsyncIteratorType: GenericType; let deferredGlobalAsyncIterableIteratorType: GenericType; let deferredGlobalAsyncGeneratorType: GenericType; let deferredGlobalTemplateStringsArrayType: ObjectType; let deferredGlobalImportMetaType: ObjectType; let deferredGlobalExtractSymbol: Symbol; let deferredGlobalOmitSymbol: Symbol; let deferredGlobalBigIntType: ObjectType; const allPotentiallyUnusedIdentifiers = new Map(); // key is file name let flowLoopStart = 0; let flowLoopCount = 0; let sharedFlowCount = 0; let flowAnalysisDisabled = false; let flowInvocationCount = 0; let lastFlowNode: FlowNode | undefined; let lastFlowNodeReachable: boolean; let flowTypeCache: Type[] | undefined; const emptyStringType = getLiteralType(""); const zeroType = getLiteralType(0); const zeroBigIntType = getLiteralType({ negative: false, base10Value: "0" }); const resolutionTargets: TypeSystemEntity[] = []; const resolutionResults: boolean[] = []; const resolutionPropertyNames: TypeSystemPropertyName[] = []; let suggestionCount = 0; const maximumSuggestionCount = 10; const mergedSymbols: Symbol[] = []; const symbolLinks: SymbolLinks[] = []; const nodeLinks: NodeLinks[] = []; const flowLoopCaches: ESMap[] = []; const flowLoopNodes: FlowNode[] = []; const flowLoopKeys: string[] = []; const flowLoopTypes: Type[][] = []; const sharedFlowNodes: FlowNode[] = []; const sharedFlowTypes: FlowType[] = []; const flowNodeReachable: (boolean | undefined)[] = []; const flowNodePostSuper: (boolean | undefined)[] = []; const potentialThisCollisions: Node[] = []; const potentialNewTargetCollisions: Node[] = []; const potentialWeakMapCollisions: Node[] = []; const awaitedTypeStack: number[] = []; const diagnostics = createDiagnosticCollection(); const suggestionDiagnostics = createDiagnosticCollection(); const typeofTypesByName: ReadonlyESMap = new Map(getEntries({ string: stringType, number: numberType, bigint: bigintType, boolean: booleanType, symbol: esSymbolType, undefined: undefinedType })); const typeofType = createTypeofType(); let _jsxNamespace: __String; let _jsxFactoryEntity: EntityName | undefined; let outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined; const subtypeRelation = new Map(); const strictSubtypeRelation = new Map(); const assignableRelation = new Map(); const comparableRelation = new Map(); const identityRelation = new Map(); const enumRelation = new Map(); const builtinGlobals = createSymbolTable(); builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol); initializeTypeChecker(); return checker; 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); 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 { 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); if (file.localJsxFactory) { return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText; } } } } } 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 markAsSynthetic(node: Node): VisitResult { setTextRangePosEnd(node, -1, -1); return visitEachChild(node, markAsSynthetic, nullTransformationContext); } } function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) { // 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. getDiagnostics(sourceFile, cancellationToken); return emitResolver; } function lookupOrIssueError(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { const diagnostic = location ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createCompilerDiagnostic(message, arg0, arg1, arg2, arg3); 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, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { const diagnostic = error(location, message, arg0, arg1, arg2, arg3); diagnostic.skippedOn = key; return diagnostic; } function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic { const diagnostic = location ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createCompilerDiagnostic(message, arg0, arg1, arg2, arg3); diagnostics.add(diagnostic); return diagnostic; } function addErrorOrSuggestion(isError: boolean, diagnostic: DiagnosticWithLocation) { if (isError) { diagnostics.add(diagnostic); } else { suggestionDiagnostics.add({ ...diagnostic, category: DiagnosticCategory.Suggestion }); } } function errorOrSuggestion(isError: boolean, location: Node, message: DiagnosticMessage | DiagnosticMessageChain, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void { // Pseudo-synthesized input node if (location.pos < 0 || location.end < 0) { if (!isError) { return; // Drop suggestions (we have no span to suggest on) } // Issue errors globally const file = getSourceFileOfNode(location); addErrorOrSuggestion(isError, "message" in message ? createFileDiagnostic(file, 0, 0, message, arg0, arg1, arg2, arg3) : createDiagnosticForFileFromMessageChain(file, message)); // eslint-disable-line no-in-operator return; } addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3) : createDiagnosticForNodeFromMessageChain(location, message)); // eslint-disable-line no-in-operator } function errorAndMaybeSuggestAwait( location: Node, maybeMissingAwait: boolean, message: DiagnosticMessage, arg0?: string | number | undefined, arg1?: string | number | undefined, arg2?: string | number | undefined, arg3?: string | number | undefined): Diagnostic { const diagnostic = error(location, message, arg0, arg1, arg2, arg3); if (maybeMissingAwait) { const related = createDiagnosticForNode(location, Diagnostics.Did_you_forget_to_use_await); addRelatedInfo(diagnostic, related); } return diagnostic; } function createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) { symbolCount++; const symbol = (new Symbol(flags | SymbolFlags.Transient, name)); symbol.checkFlags = checkFlags || 0; 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): Symbol { 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; } target = cloneSymbol(resolvedTarget); } // 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); } 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(getNameOfDeclaration(source.declarations[0]), Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, symbolToString(target)); } } else { // error 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 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}`, () => ({ firstFile, secondFile, conflictingSymbols: new Map() } as DuplicateInfoForFiles)); const conflictingSymbolInfo = getOrUpdate(filesDuplicates.conflictingSymbols, symbolName, () => ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] } as DuplicateInfoForSymbol)); addDuplicateLocations(conflictingSymbolInfo.firstFileLocations, source); addDuplicateLocations(conflictingSymbolInfo.secondFileLocations, target); } else { addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target); addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source); } } return target; function addDuplicateLocations(locs: Declaration[], symbol: Symbol): void { 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) { source.forEach((sourceSymbol, id) => { const targetSymbol = target.get(id); target.set(id, targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : sourceSymbol); }); } function mergeModuleAugmentation(moduleName: StringLiteral | Identifier): void { const moduleAugmentation = moduleName.parent; 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, /*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 addToSymbolTable(target: SymbolTable, source: SymbolTable, message: DiagnosticMessage) { source.forEach((sourceSymbol, id) => { const targetSymbol = target.get(id); if (targetSymbol) { // Error on redeclarations forEach(targetSymbol.declarations, addDeclarationDiagnostic(unescapeLeadingUnderscores(id), message)); } else { target.set(id, sourceSymbol); } }); function addDeclarationDiagnostic(id: string, message: DiagnosticMessage) { return (declaration: Declaration) => diagnostics.add(createDiagnosticForNode(declaration, message, id)); } } function getSymbolLinks(symbol: Symbol): SymbolLinks { if (symbol.flags & SymbolFlags.Transient) return symbol; const id = getSymbolId(symbol); return symbolLinks[id] || (symbolLinks[id] = new (SymbolLinks)()); } function getNodeLinks(node: Node): NodeLinks { const nodeId = getNodeId(node); return nodeLinks[nodeId] || (nodeLinks[nodeId] = new (NodeLinks)()); } function isGlobalSourceFile(node: Node) { return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(node); } function getSymbol(symbols: SymbolTable, name: __String, meaning: SymbolFlags): Symbol | undefined { if (meaning) { const symbol = getMergedSymbol(symbols.get(name)); if (symbol) { Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); if (symbol.flags & meaning) { return symbol; } if (symbol.flags & SymbolFlags.Alias) { const target = resolveAlias(symbol); // Unknown symbol means an error occurred in alias resolution, treat it as positive answer to avoid cascading errors if (target === unknownSymbol || target.flags & 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: ParameterDeclaration, 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)) || (!outFile(compilerOptions)) || 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) <= sourceFiles.indexOf(useFile); } if (declaration.pos <= usage.pos && !(isPropertyDeclaration(declaration) && isThisProperty(usage.parent) && !declaration.initializer && !declaration.exclamationToken)) { // declaration is before usage if (declaration.kind === SyntaxKind.BindingElement) { // still might be illegal if declaration and usage are both binding elements (eg var [a = b, b = b] = [1, 2]) const errorBindingElement = getAncestor(usage, SyntaxKind.BindingElement) as BindingElement; if (errorBindingElement) { return findAncestor(errorBindingElement, isBindingElement) !== findAncestor(declaration, isBindingElement) || declaration.pos < errorBindingElement.pos; } // or it might be illegal if usage happens before parent variable is declared (eg var [a] = a) return isBlockScopedNameDeclaredBeforeUse(getAncestor(declaration, SyntaxKind.VariableDeclaration) as Declaration, usage); } else if (declaration.kind === SyntaxKind.VariableDeclaration) { // still might be illegal if usage is in the initializer of the variable declaration (eg var a = a) return !isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration as VariableDeclaration, usage); } else if (isClassDeclaration(declaration)) { // still might be illegal if the usage is within a computed property name in the class (eg class A { static p = "a"; [A.p]() {} }) return !findAncestor(usage, n => isComputedPropertyName(n) && n.parent.parent === declaration); } 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 esnext+useDefineForClassFields when bar is a parameter property return !(compilerOptions.target === ScriptTarget.ESNext && !!compilerOptions.useDefineForClassFields && 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 target: "esnext" and useDefineForClassFields: 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) // or if usage is in a type context: // 1. inside a type query (typeof in type position) // 2. inside a jsdoc comment 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 (!!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage) || usageInTypeDeclaration()) { return true; } if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { if (compilerOptions.target === ScriptTarget.ESNext && !!compilerOptions.useDefineForClassFields && getContainingClass(declaration) && (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent))) { return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true); } else { return true; } } return false; function usageInTypeDeclaration() { return !!findAncestor(usage, node => isInterfaceDeclaration(node) || isTypeAliasDeclaration(node)); } 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): boolean { return !!findAncestor(usage, current => { if (current === declContainer) { return "quit"; } if (isFunctionLike(current)) { return true; } const initializerOfProperty = current.parent && current.parent.kind === SyntaxKind.PropertyDeclaration && (current.parent).initializer === current; if (initializerOfProperty) { if (hasSyntacticModifier(current.parent, ModifierFlags.Static)) { if (declaration.kind === SyntaxKind.MethodDeclaration) { return true; } } else { const isDeclarationInstanceProperty = declaration.kind === SyntaxKind.PropertyDeclaration && !hasSyntacticModifier(declaration, ModifierFlags.Static); if (!isDeclarationInstanceProperty || getContainingClass(usage) !== getContainingClass(declaration)) { return true; } } } return false; }); } /** stopAtAnyPropertyDeclaration is used for detecting ES-standard class field use-before-def errors */ function isPropertyImmediatelyReferencedWithinDeclaration(declaration: PropertyDeclaration | ParameterPropertyDeclaration, usage: Node, stopAtAnyPropertyDeclaration: boolean) { // always legal if usage is after declaration if (usage.end > 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 useOuterVariableScopeInParameter(result: Symbol, location: Node, lastLocation: Node) { const target = getEmitScriptTarget(compilerOptions); const functionLocation = location; if (isParameter(lastLocation) && functionLocation.body && result.valueDeclaration.pos >= functionLocation.body.pos && result.valueDeclaration.end <= functionLocation.body.end) { // check for several cases where we introduce temporaries that require moving the name/initializer of the parameter to the body // - static field in a class expression // - optional chaining pre-es2020 // - nullish coalesce pre-es2020 // - spread assignment in binding pattern pre-es2017 if (target >= ScriptTarget.ES2015) { const links = getNodeLinks(functionLocation); if (links.declarationRequiresScopeChange === undefined) { links.declarationRequiresScopeChange = forEach(functionLocation.parameters, requiresScopeChange) || false; } return !links.declarationRequiresScopeChange; } } return false; function requiresScopeChange(node: ParameterDeclaration): boolean { return requiresScopeChangeWorker(node.name) || !!node.initializer && requiresScopeChangeWorker(node.initializer); } function requiresScopeChangeWorker(node: Node): boolean { switch (node.kind) { case SyntaxKind.ArrowFunction: case SyntaxKind.FunctionExpression: case SyntaxKind.FunctionDeclaration: case SyntaxKind.Constructor: // do not descend into these return false; case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.PropertyAssignment: return requiresScopeChangeWorker((node as MethodDeclaration | AccessorDeclaration | PropertyAssignment).name); case SyntaxKind.PropertyDeclaration: // static properties in classes introduce temporary variables if (hasStaticModifier(node)) { return target < ScriptTarget.ESNext || !compilerOptions.useDefineForClassFields; } return requiresScopeChangeWorker((node as PropertyDeclaration).name); default: // null coalesce and optional chain pre-es2020 produce temporary variables if (isNullishCoalesce(node) || isOptionalChain(node)) { return target < ScriptTarget.ES2020; } if (isBindingElement(node) && node.dotDotDotToken && isObjectBindingPattern(node.parent)) { return target < ScriptTarget.ES2017; } if (isTypeNode(node)) return false; return forEachChild(node, requiresScopeChangeWorker) || false; } } } /** * Resolve a given name for a given meaning at a given location. An error is reported if the name was not found and * the nameNotFoundMessage argument is not undefined. Returns the resolved symbol, or undefined if no symbol with * the given name can be found. * * @param isUse If true, this will count towards --noUnusedLocals / --noUnusedParameters. */ function resolveName( location: Node | undefined, name: __String, meaning: SymbolFlags, nameNotFoundMessage: DiagnosticMessage | undefined, nameArg: __String | Identifier | undefined, isUse: boolean, excludeGlobals = false, suggestedNameNotFoundMessage?: DiagnosticMessage): Symbol | undefined { return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSymbol, suggestedNameNotFoundMessage); } function resolveNameHelper( location: Node | undefined, name: __String, meaning: SymbolFlags, nameNotFoundMessage: DiagnosticMessage | undefined, nameArg: __String | Identifier | undefined, isUse: boolean, excludeGlobals: boolean, lookup: typeof getSymbol, suggestedNameNotFoundMessage?: DiagnosticMessage): Symbol | undefined { const originalLocation = location; // needed for did-you-mean error reporting, which gathers candidates starting from the original location let result: Symbol | undefined; let lastLocation: Node | undefined; let lastSelfReferenceLocation: Node | undefined; let propertyWithInvalidInitializer: Node | undefined; let associatedDeclarationForContainingInitializerOrBindingName: ParameterDeclaration | BindingElement | undefined; let withinDeferredContext = false; const errorLocation = location; let grandparent: Node; let isInExternalModule = false; loop: while (location) { // Locals of a source file are not in scope (because they get merged into the global symbol table) if (location.locals && !isGlobalSourceFile(location)) { if (result = lookup(location.locals, name, meaning)) { let useResult = true; if (isFunctionLike(location) && lastLocation && lastLocation !== (location).body) { // symbol lookup restrictions for function-like declarations // - Type parameters of a function are in scope in the entire function declaration, including the parameter // list and return type. However, local types are only in scope in the function body. // - parameters are only in the scope of function body // This restriction does not apply to JSDoc comment types because they are parented // at a higher level than type parameters would normally be if (meaning & result.flags & SymbolFlags.Type && lastLocation.kind !== SyntaxKind.JSDocComment) { useResult = result.flags & SymbolFlags.TypeParameter // type parameters are visible in parameter list, return type and type parameter list ? lastLocation === (location).type || lastLocation.kind === SyntaxKind.Parameter || lastLocation.kind === SyntaxKind.TypeParameter // local types not visible outside the function body : false; } if (meaning & result.flags & SymbolFlags.Variable) { // expression inside parameter will lookup as normal variable scope when targeting es2015+ if (useOuterVariableScopeInParameter(result, location, lastLocation)) { useResult = false; } else if (result.flags & SymbolFlags.FunctionScopedVariable) { // parameters are visible only inside function body, parameter list and return type // technically for parameter list case here we might mix parameters and variables declared in function, // however it is detected separately when checking initializers of parameters // to make sure that they reference no variables declared after them. useResult = lastLocation.kind === SyntaxKind.Parameter || ( lastLocation === (location).type && !!findAncestor(result.valueDeclaration, isParameter) ); } } } else if (location.kind === SyntaxKind.ConditionalType) { // A type parameter declared using 'infer T' in a conditional type is visible only in // the true branch of the conditional type. useResult = lastLocation === (location).trueType; } if (useResult) { break loop; } else { result = undefined; } } } withinDeferredContext = withinDeferredContext || getIsDeferredContext(location, lastLocation); switch (location.kind) { case SyntaxKind.SourceFile: if (!isExternalOrCommonJsModule(location)) break; isInExternalModule = true; // falls through case SyntaxKind.ModuleDeclaration: const moduleExports = getSymbolOfNode(location as SourceFile | ModuleDeclaration).exports || emptySymbols; if (location.kind === SyntaxKind.SourceFile || (isModuleDeclaration(location) && location.flags & NodeFlags.Ambient && !isGlobalScopeAugmentation(location))) { // It's an external module. First see if the module has an export default and if the local // name of that export default matches. if (result = moduleExports.get(InternalSymbolName.Default)) { const localSymbol = getLocalSymbolForExportDefault(result); if (localSymbol && (result.flags & meaning) && localSymbol.escapedName === name) { break loop; } result = undefined; } // Because of module/namespace merging, a module's exports are in scope, // yet we never want to treat an export specifier as putting a member in scope. // Therefore, if the name we find is purely an export specifier, it is not actually considered in scope. // Two things to note about this: // 1. We have to check this without calling getSymbol. The problem with calling getSymbol // on an export specifier is that it might find the export specifier itself, and try to // resolve it as an alias. This will cause the checker to consider the export specifier // a circular alias reference when it might not be. // 2. We check === SymbolFlags.Alias in order to check that the symbol is *purely* // an alias. If we used &, we'd be throwing out symbols that have non alias aspects, // which is not the desired behavior. const moduleExport = moduleExports.get(name); if (moduleExport && moduleExport.flags === SymbolFlags.Alias && (getDeclarationOfKind(moduleExport, SyntaxKind.ExportSpecifier) || getDeclarationOfKind(moduleExport, SyntaxKind.NamespaceExport))) { break; } } // ES6 exports are also visible locally (except for 'default'), but commonjs exports are not (except typedefs) if (name !== InternalSymbolName.Default && (result = lookup(moduleExports, name, meaning & SymbolFlags.ModuleMember))) { if (isSourceFile(location) && location.commonJsModuleIndicator && !result.declarations.some(isJSDocTypeAlias)) { result = undefined; } else { break loop; } } break; case SyntaxKind.EnumDeclaration: if (result = lookup(getSymbolOfNode(location)!.exports!, name, meaning & SymbolFlags.EnumMember)) { break loop; } break; case SyntaxKind.PropertyDeclaration: // TypeScript 1.0 spec (April 2014): 8.4.1 // Initializer expressions for instance member variables are evaluated in the scope // of the class constructor body but are not permitted to reference parameters or // local variables of the constructor. This effectively means that entities from outer scopes // by the same name as a constructor parameter or local variable are inaccessible // in initializer expressions for instance member variables. if (!hasSyntacticModifier(location, ModifierFlags.Static)) { const ctor = findConstructorDeclaration(location.parent as ClassLikeDeclaration); if (ctor && ctor.locals) { if (lookup(ctor.locals, name, meaning & SymbolFlags.Value)) { // Remember the property node, it will be used later to report appropriate error propertyWithInvalidInitializer = location; } } } break; case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: case SyntaxKind.InterfaceDeclaration: // 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! if (result = lookup(getSymbolOfNode(location as ClassLikeDeclaration | InterfaceDeclaration).members || emptySymbols, name, meaning & SymbolFlags.Type)) { if (!isTypeParameterSymbolDeclaredInContainer(result, location)) { // ignore type parameters not declared in this container result = undefined; break; } if (lastLocation && hasSyntacticModifier(lastLocation, ModifierFlags.Static)) { // TypeScript 1.0 spec (April 2014): 3.4.1 // The scope of a type parameter extends over the entire declaration with which the type // parameter list is associated, with the exception of static member declarations in classes. error(errorLocation, Diagnostics.Static_members_cannot_reference_class_type_parameters); return undefined; } break loop; } if (location.kind === SyntaxKind.ClassExpression && meaning & SymbolFlags.Class) { const className = (location).name; if (className && name === className.escapedText) { result = location.symbol; break loop; } } break; case SyntaxKind.ExpressionWithTypeArguments: // The type parameters of a class are not in scope in the base class expression. if (lastLocation === (location).expression && (location.parent).token === SyntaxKind.ExtendsKeyword) { const container = location.parent.parent; if (isClassLike(container) && (result = lookup(getSymbolOfNode(container).members!, name, meaning & SymbolFlags.Type))) { if (nameNotFoundMessage) { error(errorLocation, Diagnostics.Base_class_expressions_cannot_reference_class_type_parameters); } return undefined; } } break; // It is not legal to reference a class's own type parameters from a computed property name that // belongs to the class. For example: // // function foo() { return '' } // class C { // <-- Class's own type parameter T // [foo()]() { } // <-- Reference to T from class's own computed property // } // case SyntaxKind.ComputedPropertyName: grandparent = location.parent.parent; if (isClassLike(grandparent) || grandparent.kind === SyntaxKind.InterfaceDeclaration) { // A reference to this grandparent's type parameters would be an error if (result = lookup(getSymbolOfNode(grandparent as ClassLikeDeclaration | InterfaceDeclaration).members!, name, meaning & SymbolFlags.Type)) { error(errorLocation, Diagnostics.A_computed_property_name_cannot_reference_a_type_parameter_from_its_containing_type); return undefined; } } break; case SyntaxKind.ArrowFunction: // when targeting ES6 or higher there is no 'arguments' in an arrow function // for lower compile targets the resolved symbol is used to emit an error if (compilerOptions.target! >= ScriptTarget.ES2015) { break; } // falls through case SyntaxKind.MethodDeclaration: case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.FunctionDeclaration: if (meaning & SymbolFlags.Variable && name === "arguments") { result = argumentsSymbol; break loop; } break; case SyntaxKind.FunctionExpression: if (meaning & SymbolFlags.Variable && name === "arguments") { result = argumentsSymbol; break loop; } if (meaning & SymbolFlags.Function) { const functionName = (location).name; if (functionName && name === functionName.escapedText) { result = location.symbol; break loop; } } break; case SyntaxKind.Decorator: // Decorators are resolved at the class declaration. Resolving at the parameter // or member would result in looking up locals in the method. // // function y() {} // class C { // method(@y x, y) {} // <-- decorator y should be resolved at the class declaration, not the parameter. // } // if (location.parent && location.parent.kind === SyntaxKind.Parameter) { location = location.parent; } // // function y() {} // class C { // @y method(x, y) {} // <-- decorator y should be resolved at the class declaration, not the method. // } // // class Decorators are resolved outside of the class to avoid referencing type parameters of that class. // // type T = number; // declare function y(x: T): any; // @param(1 as T) // <-- T should resolve to the type alias outside of class C // class C {} if (location.parent && (isClassElement(location.parent) || location.parent.kind === SyntaxKind.ClassDeclaration)) { location = location.parent; } break; case SyntaxKind.JSDocTypedefTag: case SyntaxKind.JSDocCallbackTag: case SyntaxKind.JSDocEnumTag: // js type aliases do not resolve names from their host, so skip past it location = getJSDocHost(location); break; case SyntaxKind.Parameter: if (lastLocation && ( lastLocation === (location as ParameterDeclaration).initializer || lastLocation === (location as ParameterDeclaration).name && isBindingPattern(lastLocation))) { if (!associatedDeclarationForContainingInitializerOrBindingName) { associatedDeclarationForContainingInitializerOrBindingName = location as ParameterDeclaration; } } break; case SyntaxKind.BindingElement: if (lastLocation && ( lastLocation === (location as BindingElement).initializer || lastLocation === (location as BindingElement).name && isBindingPattern(lastLocation))) { if (isParameterDeclaration(location as BindingElement) && !associatedDeclarationForContainingInitializerOrBindingName) { associatedDeclarationForContainingInitializerOrBindingName = location as BindingElement; } } break; case SyntaxKind.InferType: if (meaning & SymbolFlags.TypeParameter) { const parameterName = (location).typeParameter.name; if (parameterName && name === parameterName.escapedText) { result = (location).typeParameter.symbol; break loop; } } break; } if (isSelfReferenceLocation(location)) { lastSelfReferenceLocation = location; } lastLocation = location; location = location.parent; } // We just climbed up parents looking for the name, meaning that we started in a descendant node of `lastLocation`. // If `result === lastSelfReferenceLocation.symbol`, that means that we are somewhere inside `lastSelfReferenceLocation` looking up a name, and resolving to `lastLocation` itself. // That means that this is a self-reference of `lastLocation`, and shouldn't count this when considering whether `lastLocation` is used. if (isUse && result && (!lastSelfReferenceLocation || result !== lastSelfReferenceLocation.symbol)) { result.isReferenced! |= meaning; } if (!result) { if (lastLocation) { Debug.assert(lastLocation.kind === SyntaxKind.SourceFile); if ((lastLocation as SourceFile).commonJsModuleIndicator && name === "exports" && meaning & lastLocation.symbol.flags) { return lastLocation.symbol; } } if (!excludeGlobals) { result = lookup(globals, name, meaning); } } if (!result) { if (originalLocation && isInJSFile(originalLocation) && originalLocation.parent) { if (isRequireCall(originalLocation.parent, /*checkArgumentIsStringLiteralLike*/ false)) { return requireSymbol; } } } if (!result) { if (nameNotFoundMessage) { if (!errorLocation || !checkAndReportErrorForMissingPrefix(errorLocation, name, nameArg!) && // TODO: GH#18217 !checkAndReportErrorForExtendingInterface(errorLocation) && !checkAndReportErrorForUsingTypeAsNamespace(errorLocation, name, meaning) && !checkAndReportErrorForExportingPrimitiveType(errorLocation, name) && !checkAndReportErrorForUsingTypeAsValue(errorLocation, name, meaning) && !checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation, name, meaning) && !checkAndReportErrorForUsingValueAsType(errorLocation, name, meaning)) { let suggestion: Symbol | undefined; if (suggestedNameNotFoundMessage && suggestionCount < maximumSuggestionCount) { suggestion = getSuggestedSymbolForNonexistentSymbol(originalLocation, name, meaning); if (suggestion) { const suggestionName = symbolToString(suggestion); const diagnostic = error(errorLocation, suggestedNameNotFoundMessage, diagnosticName(nameArg!), suggestionName); if (suggestion.valueDeclaration) { addRelatedInfo( diagnostic, createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestionName) ); } } } if (!suggestion) { if (nameArg) { const lib = getSuggestedLibForNonExistentName(nameArg); if (lib) { error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg), lib); } else { error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg)); } } } suggestionCount++; } } return undefined; } // Perform extra checks only if error reporting was requested if (nameNotFoundMessage) { if (propertyWithInvalidInitializer && !(compilerOptions.target === ScriptTarget.ESNext && compilerOptions.useDefineForClassFields)) { // 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 ESNext+useDefineForClassFields because the scope semantics are different. const propertyName = (propertyWithInvalidInitializer).name; error(errorLocation, Diagnostics.Initializer_of_instance_member_variable_0_cannot_reference_identifier_1_declared_in_the_constructor, declarationNameToString(propertyName), diagnosticName(nameArg!)); return undefined; } // 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 (result && isInExternalModule && (meaning & SymbolFlags.Value) === SymbolFlags.Value && !(originalLocation!.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 (result && 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 === getSymbolOfNode(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 && lookup(root.parent.locals, candidate.escapedName, meaning) === candidate) { error(errorLocation, Diagnostics.Parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(associatedDeclarationForContainingInitializerOrBindingName.name), declarationNameToString(errorLocation)); } } if (result && errorLocation && meaning & SymbolFlags.Value && result.flags & SymbolFlags.Alias) { checkSymbolUsageInExpressionContext(result, name, errorLocation); } } return result; } function checkSymbolUsageInExpressionContext(symbol: Symbol, name: __String, useSite: Node) { if (!isValidTypeOnlyAliasUseSite(useSite)) { const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(symbol); if (typeOnlyDeclaration) { const isExport = typeOnlyDeclarationIsExport(typeOnlyDeclaration); const message = isExport ? 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 relatedMessage = isExport ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here; const unescapedName = unescapeLeadingUnderscores(name); addRelatedInfo( error(useSite, message, unescapedName), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, unescapedName)); } } } function getIsDeferredContext(location: Node, lastLocation: Node | undefined): boolean { if (location.kind !== SyntaxKind.ArrowFunction && location.kind !== SyntaxKind.FunctionExpression) { // initializers in instance property declaration of class like entities are executed in constructor and thus deferred return isTypeQueryNode(location) || (( isFunctionLikeDeclaration(location) || (location.kind === SyntaxKind.PropertyDeclaration && !hasSyntacticModifier(location, ModifierFlags.Static)) ) && (!lastLocation || lastLocation !== (location as SignatureDeclaration | PropertyDeclaration).name)); // A name is evaluated within the enclosing scope - so it shouldn't count as deferred } if (lastLocation && lastLocation === (location as FunctionExpression | ArrowFunction).name) { return false; } // generator functions and async functions are not inlined in control flow when immediately invoked if ((location as FunctionExpression | ArrowFunction).asteriskToken || hasSyntacticModifier(location, ModifierFlags.Async)) { return true; } return !getImmediatelyInvokedFunctionExpression(location); } function isSelfReferenceLocation(node: Node): boolean { switch (node.kind) { case SyntaxKind.FunctionDeclaration: case SyntaxKind.ClassDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.ModuleDeclaration: // For `namespace N { N; }` return true; default: return false; } } function diagnosticName(nameArg: __String | Identifier | PrivateIdentifier) { return isString(nameArg) ? unescapeLeadingUnderscores(nameArg as __String) : declarationNameToString(nameArg as Identifier); } function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) { for (const decl of symbol.declarations) { if (decl.kind === SyntaxKind.TypeParameter) { const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent; if (parent === container) { return !(isJSDocTemplateTag(decl.parent) && find((decl.parent.parent as JSDoc).tags!, isJSDocTypeAlias)); // TODO: GH#18217 } } } return false; } 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); let location = container; while (location) { if (isClassLike(location.parent)) { const classSymbol = getSymbolOfNode(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 && !hasSyntacticModifier(location, ModifierFlags.Static)) { const instanceType = (getDeclaredTypeOfSymbol(classSymbol)).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).expression)) { return (node).expression; } // 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, /*nameArg*/ 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, /*nameArg*/ 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 & ~SymbolFlags.NamespaceModule)) { if (isPrimitiveTypeName(name)) { 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, /*nameArg*/ undefined, /*isUse*/ false)); if (symbol && !(symbol.flags & SymbolFlags.NamespaceModule)) { 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 checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation: Node, name: __String, meaning: SymbolFlags): boolean { if (meaning & (SymbolFlags.Value & ~SymbolFlags.NamespaceModule & ~SymbolFlags.Type)) { const symbol = resolveSymbol(resolveName(errorLocation, name, SymbolFlags.NamespaceModule & ~SymbolFlags.Value, /*nameNotFoundMessage*/undefined, /*nameArg*/ 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.NamespaceModule & ~SymbolFlags.Value)) { const symbol = resolveSymbol(resolveName(errorLocation, name, (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) & ~SymbolFlags.Type, /*nameNotFoundMessage*/undefined, /*nameArg*/ 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 = find( result.declarations, 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 (compilerOptions.preserveConstEnums) { 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. * 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 === stopAt || isFunctionLike(n) ? "quit" : n === parent); } function getAnyImportSyntax(node: Node): AnyImportSyntax | 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 find(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: } */ function isAliasSymbolDeclaration(node: Node): boolean { return node.kind === SyntaxKind.ImportEqualsDeclaration || node.kind === SyntaxKind.NamespaceExportDeclaration || node.kind === SyntaxKind.ImportClause && !!(node).name || node.kind === SyntaxKind.NamespaceImport || node.kind === SyntaxKind.NamespaceExport || node.kind === SyntaxKind.ImportSpecifier || node.kind === SyntaxKind.ExportSpecifier || node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(node) || 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) || isRequireVariableDeclaration(node, /*requireStringLiteralLikeArgument*/ true); } 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); markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false); return resolved; } const resolved = getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias); checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node, resolved); return resolved; } function checkAndReportErrorForResolvingImportAliasToTypeOnlySymbol(node: ImportEqualsDeclaration, resolved: Symbol | undefined) { if (markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false)) { const typeOnlyDeclaration = getTypeOnlyAliasDeclaration(getSymbolOfNode(node))!; const isExport = typeOnlyDeclarationIsExport(typeOnlyDeclaration); const message = isExport ? Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_exported_using_export_type : Diagnostics.An_import_alias_cannot_reference_a_declaration_that_was_imported_using_import_type; const relatedMessage = isExport ? Diagnostics._0_was_exported_here : Diagnostics._0_was_imported_here; // Non-null assertion is safe because the optionality comes from ImportClause, // but if an ImportClause was the typeOnlyDeclaration, it had to have a `name`. const name = unescapeLeadingUnderscores(typeOnlyDeclaration.name!.escapedText); addRelatedInfo(error(node.moduleReference, message), createDiagnosticForNode(typeOnlyDeclaration, relatedMessage, name)); } } function resolveExportByName(moduleSymbol: Symbol, name: __String, sourceNode: TypeOnlyCompatibleAliasDeclaration | undefined, dontResolveAlias: boolean) { const exportValue = moduleSymbol.exports!.get(InternalSymbolName.ExportEquals); const exportSymbol = exportValue ? getPropertyOfType(getTypeOfSymbol(exportValue), name) : moduleSymbol.exports!.get(name); const resolved = resolveSymbol(exportSymbol, dontResolveAlias); markSymbolOfAliasDeclarationIfTypeOnly(sourceNode, exportSymbol, resolved, /*overwriteEmpty*/ false); return resolved; } function isSyntacticDefault(node: Node) { return ((isExportAssignment(node) && !node.isExportEquals) || hasSyntacticModifier(node, ModifierFlags.Default) || isExportSpecifier(node)); } function canHaveSyntheticDefault(file: SourceFile | undefined, moduleSymbol: Symbol, dontResolveAlias: boolean) { if (!allowSyntheticDefaultImports) { return false; } // Declaration files (and ambient modules) if (!file || file.isDeclarationFile) { // Definitely cannot have a synthetic default if they have a syntactic default member specified const defaultExportSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, /*sourceNode*/ undefined, /*dontResolveAlias*/ true); // Dont resolve alias because we want the immediately exported symbol's declaration if (defaultExportSymbol && some(defaultExportSymbol.declarations, isSyntacticDefault)) { return false; } // It _might_ still be incorrect to assume there is no __esModule marker on the import at runtime, even if there is no `default` member // So we check a bit more, if (resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), /*sourceNode*/ undefined, dontResolveAlias)) { // If there is an `__esModule` specified in the declaration (meaning someone explicitly added it or wrote it in their code), // it definitely is a module and does not have a synthetic default return false; } // There are _many_ declaration files not written with esmodules in mind that still get compiled into a format with __esModule set // Meaning there may be no default at runtime - however to be on the permissive side, we allow access to a synthetic default member // as there is no marker to indicate if the accompanying JS has `__esModule` or not, or is even native esm return true; } // TypeScript files never have a synthetic default (as they are always emitted with an __esModule marker) _unless_ they contain an export= statement if (!isSourceFileJS(file)) { return hasExportAssignmentSymbol(moduleSymbol); } // JS files have a synthetic default if they do not contain ES2015+ module syntax (export = is not valid in js) _and_ do not have an __esModule marker return !file.externalModuleIndicator && !resolveExportByName(moduleSymbol, escapeLeadingUnderscores("__esModule"), /*sourceNode*/ undefined, dontResolveAlias); } function getTargetOfImportClause(node: ImportClause, dontResolveAlias: boolean): Symbol | undefined { const moduleSymbol = resolveExternalModuleName(node, node.parent.moduleSpecifier); if (moduleSymbol) { let exportDefaultSymbol: Symbol | undefined; if (isShorthandAmbientModuleSymbol(moduleSymbol)) { exportDefaultSymbol = moduleSymbol; } else { exportDefaultSymbol = resolveExportByName(moduleSymbol, InternalSymbolName.Default, node, dontResolveAlias); } const file = find(moduleSymbol.declarations, isSourceFile); const hasSyntheticDefault = canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias); if (!exportDefaultSymbol && !hasSyntheticDefault) { if (hasExportAssignmentSymbol(moduleSymbol)) { 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); addRelatedInfo(err, createDiagnosticForNode( exportAssignment, Diagnostics.This_module_is_declared_with_using_export_and_can_only_be_used_with_a_default_import_when_using_the_0_flag, compilerOptionName )); } else { reportNonDefaultExport(moduleSymbol, node); } } else if (hasSyntheticDefault) { // 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, /*overwriteTypeOnly*/ false); return resolved; } markSymbolOfAliasDeclarationIfTypeOnly(node, exportDefaultSymbol, /*finalTarget*/ undefined, /*overwriteTypeOnly*/ false); return exportDefaultSymbol; } } 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 = find(exportStar.declarations, 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, /*suppressUsageError*/ 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, /*suppressUsageError*/ 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); 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, name: Identifier, specifier: Declaration, dontResolveAlias: boolean): Symbol | undefined { if (symbol.flags & SymbolFlags.Module) { const exportSymbol = getExportsOfSymbol(symbol).get(name.escapedText); const resolved = resolveSymbol(exportSymbol, dontResolveAlias); markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false); return resolved; } } function getPropertyOfVariable(symbol: Symbol, name: __String): Symbol | undefined { if (symbol.flags & SymbolFlags.Variable) { const typeAnnotation = (symbol.valueDeclaration).type; if (typeAnnotation) { return resolveSymbol(getPropertyOfType(getTypeFromTypeNode(typeAnnotation), name)); } } } function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, specifier: ImportOrExportSpecifier | BindingElement | PropertyAccessExpression, dontResolveAlias = false): Symbol | undefined { const moduleSpecifier = getExternalModuleRequireArgument(node) || (node as ImportDeclaration | ExportDeclaration).moduleSpecifier!; const moduleSymbol = resolveExternalModuleName(node, moduleSpecifier)!; // TODO: GH#18217 const name = !isPropertyAccessExpression(specifier) && specifier.propertyName || specifier.name; if (!isIdentifier(name)) { return undefined; } const suppressInteropError = name.escapedText === InternalSymbolName.Default && !!(compilerOptions.allowSyntheticDefaultImports || compilerOptions.esModuleInterop); const targetSymbol = resolveESModuleSymbol(moduleSymbol, moduleSpecifier, dontResolveAlias, suppressInteropError); if (targetSymbol) { if (name.escapedText) { 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), name.escapedText); } else { symbolFromVariable = getPropertyOfVariable(targetSymbol, name.escapedText); } // if symbolFromVariable is export - get its final target symbolFromVariable = resolveSymbol(symbolFromVariable, dontResolveAlias); let symbolFromModule = getExportOfModule(targetSymbol, name, specifier, dontResolveAlias); if (symbolFromModule === undefined && name.escapedText === InternalSymbolName.Default) { const file = find(moduleSymbol.declarations, isSourceFile); if (canHaveSyntheticDefault(file, moduleSymbol, dontResolveAlias)) { symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); } } const symbol = symbolFromModule && symbolFromVariable && symbolFromModule !== symbolFromVariable ? combineValueAndTypeSymbols(symbolFromVariable, symbolFromModule) : symbolFromModule || symbolFromVariable; if (!symbol) { const moduleName = getFullyQualifiedName(moduleSymbol, node); const declarationName = declarationNameToString(name); const suggestion = getSuggestedSymbolForNonexistentModule(name, targetSymbol); 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); } } } return symbol; } } } function reportNonExportedMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, name: Identifier, declarationName: string, moduleSymbol: Symbol, moduleName: string): void { const localSymbol = moduleSymbol.valueDeclaration.locals?.get(name.escapedText); 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); 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: ImportDeclaration | ExportDeclaration | VariableDeclaration, name: Identifier, declarationName: string, moduleName: string) { if (moduleKind >= ModuleKind.ES2015) { const message = compilerOptions.esModuleInterop ? 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 = compilerOptions.esModuleInterop ? 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 = compilerOptions.esModuleInterop ? 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 { 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 { 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 resolved = node.parent.parent.moduleSpecifier ? getExternalModuleMember(node.parent.parent, node, dontResolveAlias) : resolveEntityName(node.propertyName || node.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 getTargetOfPropertyAssignment(node: PropertyAssignment, dontRecursivelyResolve: boolean): Symbol | undefined { const expression = node.initializer; return getTargetOfAliasLikeExpression(expression, dontRecursivelyResolve); } 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, dontRecursivelyResolve); case SyntaxKind.NamespaceExport: return getTargetOfNamespaceExport(node, dontRecursivelyResolve); case SyntaxKind.ImportSpecifier: case SyntaxKind.BindingElement: return getTargetOfImportSpecifier(node as ImportSpecifier | BindingElement, dontRecursivelyResolve); case SyntaxKind.ExportSpecifier: return getTargetOfExportSpecifier(node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, dontRecursivelyResolve); case SyntaxKind.ExportAssignment: case SyntaxKind.BinaryExpression: return getTargetOfExportAssignment((node), dontRecursivelyResolve); case SyntaxKind.NamespaceExportDeclaration: return getTargetOfNamespaceExportDeclaration(node, dontRecursivelyResolve); case SyntaxKind.ShorthandPropertyAssignment: return resolveEntityName((node as ShorthandPropertyAssignment).name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/ true, dontRecursivelyResolve); case SyntaxKind.PropertyAssignment: return getTargetOfPropertyAssignment(node as PropertyAssignment, 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.target) { links.target = resolvingSymbol; const node = getDeclarationOfAliasSymbol(symbol); if (!node) return Debug.fail(); const target = getTargetOfAliasDeclaration(node); if (links.target === resolvingSymbol) { links.target = target || unknownSymbol; } else { error(node, Diagnostics.Circular_definition_of_import_alias_0, symbolToString(symbol)); } } else if (links.target === resolvingSymbol) { links.target = unknownSymbol; } return links.target; } function tryResolveAlias(symbol: Symbol): Symbol | undefined { const links = getSymbolLinks(symbol); if (links.target !== resolvingSymbol) { return resolveAlias(symbol); } return undefined; } /** * 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 * 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. * @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` */ function markSymbolOfAliasDeclarationIfTypeOnly( aliasDeclaration: Declaration | undefined, immediateTarget: Symbol | undefined, finalTarget: Symbol | undefined, overwriteEmpty: boolean, ): 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 = getSymbolOfNode(aliasDeclaration); if (isTypeOnlyImportOrExportDeclaration(aliasDeclaration)) { const links = getSymbolLinks(sourceSymbol); links.typeOnlyDeclaration = aliasDeclaration; 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): TypeOnlyCompatibleAliasDeclaration | undefined { if (!(symbol.flags & SymbolFlags.Alias)) { return undefined; } const links = getSymbolLinks(symbol); return links.typeOnlyDeclaration || undefined; } function markExportAsReferenced(node: ImportEqualsDeclaration | ExportSpecifier) { const symbol = getSymbolOfNode(node); const target = resolveAlias(symbol); if (target) { const markAlias = target === unknownSymbol || ((target.flags & SymbolFlags.Value) && !isConstEnumOrConstEnumOnlyModule(target) && !getTypeOnlyAliasDeclaration(symbol)); if (markAlias) { 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) { 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)) { const target = resolveSymbol(symbol); if (target === unknownSymbol || target.flags & SymbolFlags.Value) { // import foo = checkExpressionCached(node.moduleReference); } } } } // Aliases that resolve to const enums are not marked as referenced because they are not emitted, // but their usage in value positions must be tracked to determine if the import can be type-only. function markConstEnumAliasAsReferenced(symbol: Symbol) { const links = getSymbolLinks(symbol); if (!links.constEnumReferenced) { links.constEnumReferenced = true; } } // 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; } // 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); } /** * 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.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true)); 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 (isInJSFile(name)) { if (namespace.valueDeclaration && 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) { if (!ignoreErrors) { const namespaceName = getFullyQualifiedName(namespace); const declarationName = declarationNameToString(right); const suggestion = getSuggestedSymbolForNonexistentModule(right, namespace); suggestion ? error(right, Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2, namespaceName, declarationName, symbolToString(suggestion)) : error(right, Diagnostics.Namespace_0_has_no_exported_member_1, namespaceName, declarationName); } return undefined; } } else { throw Debug.assertNever(name, "Unknown entity name kind."); } Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here."); if (!nodeIsSynthesized(name) && isEntityName(name) && (symbol.flags & SymbolFlags.Alias || name.parent.kind === SyntaxKind.ExportAssignment)) { markSymbolOfAliasDeclarationIfTypeOnly(getAliasDeclarationFromName(name), symbol, /*finalTarget*/ undefined, /*overwriteEmpty*/ true); } return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol); } /** * 1. For prototype-property methods like `A.prototype.m = function () ...`, try to resolve names in the scope of `A` too. * Note that prototype-property assignment to locations outside the current file (eg globals) doesn't work, so * name resolution won't work either. * 2. For property assignments like `{ x: function f () { } }`, try to resolve names in the scope of `f` too. */ function resolveEntityNameFromAssignmentDeclaration(name: Identifier, meaning: SymbolFlags) { if (isJSDocTypeReference(name.parent)) { const secondaryLocation = getAssignmentDeclarationLocation(name.parent); if (secondaryLocation) { return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true); } } } function getAssignmentDeclarationLocation(node: TypeReferenceNode): Node | undefined { const typeAlias = findAncestor(node, node => !(isJSDocNode(node) || node.flags & NodeFlags.JSDoc) ? "quit" : isJSDocTypeAlias(node)); if (typeAlias) { return; } const host = getJSDocHost(node); if (isExpressionStatement(host) && isBinaryExpression(host.expression) && getAssignmentDeclarationKind(host.expression) === AssignmentDeclarationKind.PrototypeProperty) { // X.prototype.m = /** @param {K} p */ function () { } <-- look for K on X's declaration const symbol = getSymbolOfNode(host.expression.left); if (symbol) { return getDeclarationOfJSPrototypeContainer(symbol); } } if ((isObjectLiteralMethod(host) || isPropertyAssignment(host)) && isBinaryExpression(host.parent.parent) && getAssignmentDeclarationKind(host.parent.parent) === AssignmentDeclarationKind.Prototype) { // X.prototype = { /** @param {K} p */m() { } } <-- look for K on X's declaration const symbol = getSymbolOfNode(host.parent.parent.left); if (symbol) { return getDeclarationOfJSPrototypeContainer(symbol); } } const sig = getEffectiveJSDocHost(node); if (sig && isFunctionLike(sig)) { const symbol = getSymbolOfNode(sig); return symbol && symbol.valueDeclaration; } } function getDeclarationOfJSPrototypeContainer(symbol: Symbol) { const decl = symbol.parent!.valueDeclaration; if (!decl) { return undefined; } const initializer = isAssignmentDeclaration(decl) ? getAssignedExpandoInitializer(decl) : hasOnlyExpressionInitializer(decl) ? getDeclaredExpandoInitializer(decl) : undefined; return initializer || decl; } /** * Get the real symbol of a declaration with an expando initializer. * * Normally, declarations have an associated symbol, but when a declaration has an expando * initializer, the expando's symbol is the one that has all the members merged into it. */ function getExpandoSymbol(symbol: Symbol): Symbol | undefined { const decl = symbol.valueDeclaration; if (!decl || !isInJSFile(decl) || symbol.flags & SymbolFlags.TypeAlias || getExpandoInitializer(decl, /*isPrototypeAssignment*/ false)) { return undefined; } const init = isVariableDeclaration(decl) ? getDeclaredExpandoInitializer(decl) : getAssignedExpandoInitializer(decl); if (init) { const initSymbol = getSymbolOfNode(init); if (initSymbol) { return mergeJSSymbols(initSymbol, symbol); } } } function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression, ignoreErrors?: boolean): Symbol | undefined { const isClassic = getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Classic; const errorMessage = isClassic? Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option : Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations; return resolveExternalModuleNameWorker(location, moduleReferenceExpression, ignoreErrors ? undefined : errorMessage); } function resolveExternalModuleNameWorker(location: Node, moduleReferenceExpression: Expression, moduleNotFoundError: DiagnosticMessage | undefined, isForAugmentation = false): Symbol | undefined { return isStringLiteralLike(moduleReferenceExpression) ? resolveExternalModule(location, moduleReferenceExpression.text, moduleNotFoundError, moduleReferenceExpression, isForAugmentation) : undefined; } function resolveExternalModule(location: Node, moduleReference: string, moduleNotFoundError: DiagnosticMessage | undefined, errorNode: Node, isForAugmentation = false): Symbol | undefined { if (startsWith(moduleReference, "@types/")) { const diag = Diagnostics.Cannot_import_type_declaration_files_Consider_importing_0_instead_of_1; const withoutAtTypePrefix = removePrefix(moduleReference, "@types/"); error(errorNode, diag, withoutAtTypePrefix, moduleReference); } const ambientModule = tryFindAmbientModule(moduleReference, /*withAugmentations*/ true); if (ambientModule) { return ambientModule; } const currentSourceFile = getSourceFileOfNode(location); const resolvedModule = getResolvedModule(currentSourceFile, moduleReference)!; // TODO: GH#18217 const resolutionDiagnostic = resolvedModule && getResolutionDiagnostic(compilerOptions, resolvedModule); const sourceFile = resolvedModule && !resolutionDiagnostic && host.getSourceFile(resolvedModule.resolvedFileName); if (sourceFile) { if (sourceFile.symbol) { if (resolvedModule.isExternalLibraryImport && !resolutionExtensionIsTSOrJson(resolvedModule.extension)) { errorOnImplicitAnyModule(/*isError*/ false, errorNode, resolvedModule, moduleReference); } // merged symbol is module declaration symbol combined with all augmentations return getMergedSymbol(sourceFile.symbol); } if (moduleNotFoundError) { // 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); } } // 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, 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.getProjectReferenceRedirect(resolvedModule.resolvedFileName); if (redirect) { error(errorNode, Diagnostics.Output_file_0_has_not_been_built_from_source_file_1, redirect, resolvedModule.resolvedFileName); return undefined; } } if (resolutionDiagnostic) { error(errorNode, resolutionDiagnostic, moduleReference, resolvedModule.resolvedFileName); } else { const tsExtension = tryExtractTSExtension(moduleReference); if (tsExtension) { const diag = Diagnostics.An_import_path_cannot_end_with_a_0_extension_Consider_importing_1_instead; error(errorNode, diag, tsExtension, removeExtension(moduleReference, tsExtension)); } else if (!compilerOptions.resolveJsonModule && fileExtensionIs(moduleReference, Extension.Json) && getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs && hasJsonModuleEmitEnabled(compilerOptions)) { error(errorNode, Diagnostics.Cannot_find_module_0_Consider_using_resolveJsonModule_to_import_module_with_json_extension, moduleReference); } else { error(errorNode, moduleNotFoundError, moduleReference); } } } return undefined; } function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void { const errorInfo = !isExternalModuleNameRelative(moduleReference) && packageId ? typesPackageExists(packageId.name) ? chainDiagnosticMessages( /*details*/ undefined, Diagnostics.If_the_0_package_actually_exposes_this_module_consider_sending_a_pull_request_to_amend_https_Colon_Slash_Slashgithub_com_SlashDefinitelyTyped_SlashDefinitelyTyped_Slashtree_Slashmaster_Slashtypes_Slash_1, packageId.name, mangleScopedPackageName(packageId.name)) : chainDiagnosticMessages( /*details*/ undefined, Diagnostics.Try_npm_i_save_dev_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0, moduleReference, mangleScopedPackageName(packageId.name)) : undefined; errorOrSuggestion(isError, errorNode, chainDiagnosticMessages( errorInfo, Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type, moduleReference, resolvedFileName)); } function typesPackageExists(packageName: string): boolean { return getPackagesSet().has(getTypesPackageName(packageName)); } function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol; function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined; function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol { 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); }); 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; } if (compilerOptions.esModuleInterop) { const referenceParent = referencingLocation.parent; if ( (isImportDeclaration(referenceParent) && getNamespaceDeclarationNode(referenceParent)) || isImportCall(referenceParent) ) { const type = getTypeOfSymbol(symbol); let sigs = getSignaturesOfStructuredType(type, SignatureKind.Call); if (!sigs || !sigs.length) { sigs = getSignaturesOfStructuredType(type, SignatureKind.Construct); } if (sigs && sigs.length) { const moduleType = getTypeWithSyntheticDefaultImportType(type, symbol, moduleSymbol!); // Create a new symbol which has the module's type less the call and construct signatures const result = createSymbol(symbol.flags, symbol.escapedName); result.declarations = symbol.declarations ? symbol.declarations.slice() : []; result.parent = symbol.parent; result.target = symbol; result.originatingImport = referenceParent; 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); const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.stringIndexInfo, resolvedModuleType.numberIndexInfo); return result; } } } } return symbol; } function hasExportAssignmentSymbol(moduleSymbol: Symbol): boolean { return moduleSymbol.exports!.get(InternalSymbolName.ExportEquals) !== undefined; } function getExportsOfModuleAsArray(moduleSymbol: Symbol): Symbol[] { return symbolsToArray(getExportsOfModule(moduleSymbol)); } function getExportsAndPropertiesOfModule(moduleSymbol: Symbol): Symbol[] { const exports = getExportsOfModuleAsArray(moduleSymbol); const exportEquals = resolveExternalModuleSymbol(moduleSymbol); if (exportEquals !== moduleSymbol) { addRange(exports, getPropertiesOfType(getTypeOfSymbol(exportEquals))); } return exports; } 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 type.flags & TypeFlags.Primitive || getObjectFlags(type) & ObjectFlags.Class || isArrayOrTupleLikeType(type) ? undefined : getPropertyOfType(type, memberName); } 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); return links.resolvedExports || (links.resolvedExports = getExportsOfModuleWorker(moduleSymbol)); } interface ExportCollisionTracker { specifierText: string; exportsWithDuplicate: ExportDeclaration[]; } type ExportCollisionTrackerTable = UnderscoreEscapedMap; /** * 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!) } as ExportCollisionTracker); } } 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): SymbolTable { const visitedSymbols: Symbol[] = []; // A module defined by an 'export=' consists of one export that needs to be resolved moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); return visit(moduleSymbol) || emptySymbols; // 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): SymbolTable | undefined { 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(); for (const node of exportStars.declarations) { const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); const exportedSymbols = visit(resolvedModule); 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); } 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 getSymbolOfNode(node: Declaration): Symbol; function getSymbolOfNode(node: Node): Symbol | undefined; function getSymbolOfNode(node: Node): Symbol | undefined { return getMergedSymbol(node.symbol && getLateBoundSymbol(node.symbol)); } function getParentOfSymbol(symbol: Symbol): Symbol | undefined { return getMergedSymbol(symbol.parent && getLateBoundSymbol(symbol.parent)); } 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 = getSymbolOfNode(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)) { const additionalContainers = mapDefined(container.declarations, fileSymbolIfFileSymbolExportEqualsContainer); const reexportContainers = enclosingDeclaration && getAlternativeContainingModules(symbol, enclosingDeclaration); const objectLiteralContainer = getVariableDeclarationOfObjectLiteral(container, meaning); if (enclosingDeclaration && getAccessibleSymbolChain(container, enclosingDeclaration, SymbolFlags.Namespace, /*externalOnly*/ false)) { return append(concatenate(concatenate([container], additionalContainers), reexportContainers), objectLiteralContainer); // This order expresses a preference for the real container if it is in scope } const res = append(append(additionalContainers, container), objectLiteralContainer); return concatenate(res, reexportContainers); } const candidates = mapDefined(symbol.declarations, d => { if (!isAmbientModule(d) && d.parent && hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) { return getSymbolOfNode(d.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 getSymbolOfNode(getSourceFileOfNode(d)); } checkExpressionCached(d.parent.left.expression); return getNodeLinks(d.parent.left.expression).resolvedSymbol; } }); if (!length(candidates)) { return undefined; } return mapDefined(candidates, candidate => getAliasForSymbolInContainer(candidate, symbol) ? candidate : undefined); 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 getSymbolOfNode(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): boolean { return !!(symbol.flags & SymbolFlags.Value || symbol.flags & SymbolFlags.Alias && resolveAlias(symbol).flags & SymbolFlags.Value && !getTypeOnlyAliasDeclaration(symbol)); } function findConstructorDeclaration(node: ClassLikeDeclaration): ConstructorDeclaration | undefined { const members = node.members; for (const member of members) { if (member.kind === SyntaxKind.Constructor && nodeIsPresent((member).body)) { return member; } } } function createType(flags: TypeFlags): Type { const result = new Type(checker, flags); typeCount++; result.id = typeCount; typeCatalog.push(result); return result; } function createIntrinsicType(kind: TypeFlags, intrinsicName: string, objectFlags: ObjectFlags = 0): IntrinsicType { const type = createType(kind); type.intrinsicName = intrinsicName; type.objectFlags = objectFlags; return type; } function createBooleanType(trueFalseTypes: readonly Type[]): IntrinsicType & UnionType { const type = getUnionType(trueFalseTypes); type.flags |= TypeFlags.Boolean; type.intrinsicName = "boolean"; return type; } function createObjectType(objectFlags: ObjectFlags, symbol?: Symbol): ObjectType { const type = createType(TypeFlags.Object); type.objectFlags = objectFlags; type.symbol = symbol!; type.members = undefined; type.properties = undefined; type.callSignatures = undefined; type.constructSignatures = undefined; type.stringIndexInfo = undefined; type.numberIndexInfo = undefined; return type; } function createTypeofType() { return getUnionType(arrayFrom(typeofEQFacts.keys(), getLiteralType)); } function createTypeParameter(symbol?: Symbol) { const type = createType(TypeFlags.TypeParameter); if (symbol) type.symbol = symbol; return type; } // 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 (!isReservedMemberName(id) && symbolIsValue(symbol)) { (result || (result = [])).push(symbol); } }); return result || emptyArray; } function setStructuredTypeMembers(type: StructuredType, members: SymbolTable, callSignatures: readonly Signature[], constructSignatures: readonly Signature[], stringIndexInfo: IndexInfo | undefined, numberIndexInfo: IndexInfo | undefined): ResolvedType { (type).members = members; (type).properties = members === emptySymbols ? emptyArray : getNamedMembers(members); (type).callSignatures = callSignatures; (type).constructSignatures = constructSignatures; (type).stringIndexInfo = stringIndexInfo; (type).numberIndexInfo = numberIndexInfo; return type; } function createAnonymousType(symbol: Symbol | undefined, members: SymbolTable, callSignatures: readonly Signature[], constructSignatures: readonly Signature[], stringIndexInfo: IndexInfo | undefined, numberIndexInfo: IndexInfo | undefined): ResolvedType { return setStructuredTypeMembers(createObjectType(ObjectFlags.Anonymous, symbol), members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); } function forEachSymbolTableInScope(enclosingDeclaration: Node | undefined, callback: (symbolTable: SymbolTable) => 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 (location.locals && !isGlobalSourceFile(location)) { if (result = callback(location.locals)) { return result; } } switch (location.kind) { case SyntaxKind.SourceFile: if (!isExternalOrCommonJsModule(location)) { break; } // falls through case SyntaxKind.ModuleDeclaration: const sym = getSymbolOfNode(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)) { 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: UnderscoreEscapedMap | undefined; // TODO: Should this filtered table be cached in some way? (getSymbolOfNode(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))) { return result; } break; } } return callback(globals); } 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: ESMap = new Map()): Symbol[] | undefined { if (!(symbol && !isPropertyOrMethodDeclarationSymbol(symbol))) { return undefined; } const id = getSymbolId(symbol); let visitedSymbolTables = visitedSymbolTablesMap.get(id); if (!visitedSymbolTables) { visitedSymbolTablesMap.set(id, visitedSymbolTables = []); } return forEachSymbolTableInScope(enclosingDeclaration, getAccessibleSymbolChainFromSymbolTable); /** * @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): Symbol[] | undefined { if (!pushIfUnique(visitedSymbolTables!, symbols)) { return undefined; } const result = trySymbolTable(symbols, ignoreQualification); 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): 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)) // 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), /*aliasSymbol*/ 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 symbolFromSymbolTable = (symbolFromSymbolTable.flags & SymbolFlags.Alias && !getDeclarationOfKind(symbolFromSymbolTable, SyntaxKind.ExportSpecifier)) ? resolveAlias(symbolFromSymbolTable) : symbolFromSymbolTable; if (symbolFromSymbolTable.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; } } else 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) }; } } // 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 && getSymbolOfNode(node); } function hasExternalModuleSymbol(declaration: Node) { return isAmbientModule(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(declaration)); } function hasNonGlobalAugmentationExternalModuleSymbol(declaration: Node) { return isModuleWithStringLiteralName(declaration) || (declaration.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(declaration)); } 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 (symbol.flags & SymbolFlags.Alias && isBindingElement(declaration) && 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); } // 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 isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult { // get symbol of the first identifier of the entityName let meaning: SymbolFlags; if (entityName.parent.kind === SyntaxKind.TypeQuery || isExpressionWithTypeArgumentsInClassExtendsClause(entityName.parent) || entityName.parent.kind === SyntaxKind.ComputedPropertyName) { // Typeof value meaning = SymbolFlags.Value | SymbolFlags.ExportValue; } else if (entityName.kind === SyntaxKind.QualifiedName || entityName.kind === SyntaxKind.PropertyAccessExpression || entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration) { // 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; } const firstIdentifier = getFirstIdentifier(entityName); const symbol = resolveName(enclosingDeclaration, firstIdentifier.escapedText, meaning, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); if (symbol && symbol.flags & SymbolFlags.TypeParameter && meaning & SymbolFlags.Type) { return { accessibility: SymbolAccessibility.Accessible }; } // Verify if the symbol is accessible return (symbol && hasVisibleDeclarations(symbol, /*shouldComputeAliasToMakeVisible*/ true)) || { 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; 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) { nodeFlags |= NodeBuilderFlags.DoNotIncludeSymbolChain; } const builder = flags & SymbolFormatFlags.AllowAnyNodeKind ? nodeBuilder.symbolToExpression : nodeBuilder.symbolToEntityName; return writer ? symbolToStringWorker(writer).getText() : usingSingleLineStringWriter(symbolToStringWorker); function symbolToStringWorker(writer: EmitTextWriter) { const entity = builder(symbol, meaning!, enclosingDeclaration, nodeFlags)!; // TODO: GH#18217 // add neverAsciiEscape for GH#39027 const printer = enclosingDeclaration?.kind === SyntaxKind.SourceFile ? createPrinter({ removeComments: true, neverAsciiEscape: true }) : createPrinter({ removeComments: true }); 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): 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); const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true }); 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("")): string { const noTruncation = compilerOptions.noErrorTruncation || flags & TypeFormatFlags.NoTruncation; const typeNode = nodeBuilder.typeToTypeNode(type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | (noTruncation ? NodeBuilderFlags.NoTruncation : 0), writer); if (typeNode === undefined) return Debug.fail("should always get typenode"); const options = { removeComments: true }; const printer = createPrinter(options); const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); printer.writeNode(EmitHint.Unspecified, typeNode, /*sourceFile*/ sourceFile, writer); const result = writer.getText(); const maxLength = 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) || !!(getObjectFlags(type) & ObjectFlags.IsClassInstanceClone)); } function createNodeBuilder() { return { typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typeToTypeNodeHelper(type, context)), indexInfoToIndexSignatureDeclaration: (indexInfo: IndexInfo, kind: IndexKind, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => indexInfoToIndexSignatureDeclarationHelper(indexInfo, kind, context, /*typeNode*/ undefined)), signatureToSignatureDeclaration: (signature: Signature, kind: SignatureDeclaration["kind"], enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => signatureToSignatureDeclarationHelper(signature, kind, context)), symbolToEntityName: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => symbolToName(symbol, context, meaning, /*expectsIdentifier*/ false)), symbolToExpression: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => symbolToExpression(symbol, context, meaning)), symbolToTypeParameterDeclarations: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typeParametersToTypeParameterDeclarations(symbol, context)), symbolToParameterDeclaration: (symbol: Symbol, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => symbolToParameterDeclaration(symbol, context)), typeParameterToDeclaration: (parameter: TypeParameter, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => withContext(enclosingDeclaration, flags, tracker, context => typeParameterToDeclaration(parameter, context)), symbolTableToDeclarationStatements: (symbolTable: SymbolTable, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker, bundled?: boolean) => withContext(enclosingDeclaration, flags, tracker, context => symbolTableToDeclarationStatements(symbolTable, context, bundled)), }; function withContext(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, tracker: SymbolTracker | undefined, cb: (context: NodeBuilderContext) => T): T | undefined { Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); const context: NodeBuilderContext = { enclosingDeclaration, flags: flags || NodeBuilderFlags.None, // If no full tracker is provided, fake up a dummy one with a basic limited-functionality moduleResolverHost tracker: tracker && tracker.trackSymbol ? tracker : { trackSymbol: noop, moduleResolverHost: flags! & NodeBuilderFlags.DoNotIncludeSymbolChain ? { getCommonSourceDirectory: !!(host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "", getSourceFiles: () => host.getSourceFiles(), getCurrentDirectory: () => host.getCurrentDirectory(), getSymlinkCache: maybeBind(host, host.getSymlinkCache), useCaseSensitiveFileNames: maybeBind(host, host.useCaseSensitiveFileNames), redirectTargetsMap: host.redirectTargetsMap, getProjectReferenceRedirect: fileName => host.getProjectReferenceRedirect(fileName), isSourceOfProjectReferenceRedirect: fileName => host.isSourceOfProjectReferenceRedirect(fileName), fileExists: fileName => host.fileExists(fileName), } : undefined }, encounteredError: false, visitedTypes: undefined, symbolDepth: undefined, inferTypeParameters: undefined, approximateLength: 0 }; const resultingNode = cb(context); if (context.truncating && context.flags & NodeBuilderFlags.NoTruncation) { context.tracker?.reportTruncationError?.(); } return context.encounteredError ? undefined : resultingNode; } function checkTruncationLength(context: NodeBuilderContext): boolean { if (context.truncating) return context.truncating; return context.truncating = context.approximateLength > ((context.flags & NodeBuilderFlags.NoTruncation) ? noTruncationMaximumTruncationLength : defaultMaximumTruncationLength); } function typeToTypeNodeHelper(type: Type, context: NodeBuilderContext): TypeNode { if (cancellationToken && cancellationToken.throwIfCancellationRequested) { cancellationToken.throwIfCancellationRequested(); } const inTypeAlias = context.flags & NodeBuilderFlags.InTypeAlias; context.flags &= ~NodeBuilderFlags.InTypeAlias; if (!type) { if (!(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) { context.encounteredError = true; return undefined!; // TODO: GH#18217 } context.approximateLength += 3; return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); } if (!(context.flags & NodeBuilderFlags.NoTypeReduction)) { type = getReducedType(type); } if (type.flags & TypeFlags.Any) { context.approximateLength += 3; return factory.createKeywordTypeNode(type === intrinsicMarkerType ? SyntaxKind.IntrinsicKeyword : SyntaxKind.AnyKeyword); } if (type.flags & TypeFlags.Unknown) { return factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword); } if (type.flags & TypeFlags.String) { context.approximateLength += 6; return factory.createKeywordTypeNode(SyntaxKind.StringKeyword); } if (type.flags & TypeFlags.Number) { context.approximateLength += 6; return factory.createKeywordTypeNode(SyntaxKind.NumberKeyword); } if (type.flags & TypeFlags.BigInt) { context.approximateLength += 6; return factory.createKeywordTypeNode(SyntaxKind.BigIntKeyword); } if (type.flags & TypeFlags.Boolean) { context.approximateLength += 7; return factory.createKeywordTypeNode(SyntaxKind.BooleanKeyword); } if (type.flags & TypeFlags.EnumLiteral && !(type.flags & TypeFlags.Union)) { const parentSymbol = getParentOfSymbol(type.symbol)!; const parentName = symbolToTypeNode(parentSymbol, context, SymbolFlags.Type); if (getDeclaredTypeOfSymbol(parentSymbol) === type) { return parentName; } const memberName = symbolName(type.symbol); if (isIdentifierText(memberName, ScriptTarget.ES3)) { return appendReferenceToType( parentName as TypeReferenceNode | ImportTypeNode, factory.createTypeReferenceNode(memberName, /*typeArguments*/ undefined) ); } if (isImportTypeNode(parentName)) { (parentName as any).isTypeOf = true; // mutably update, node is freshly manufactured anyhow return factory.createIndexedAccessTypeNode(parentName, factory.createLiteralTypeNode(factory.createStringLiteral(memberName))); } else if (isTypeReferenceNode(parentName)) { return factory.createIndexedAccessTypeNode(factory.createTypeQueryNode(parentName.typeName), factory.createLiteralTypeNode(factory.createStringLiteral(memberName))); } else { return Debug.fail("Unhandled type node kind returned from `symbolToTypeNode`."); } } if (type.flags & TypeFlags.EnumLike) { return symbolToTypeNode(type.symbol, context, SymbolFlags.Type); } if (type.flags & TypeFlags.StringLiteral) { context.approximateLength += ((type).value.length + 2); return factory.createLiteralTypeNode(setEmitFlags(factory.createStringLiteral((type).value, !!(context.flags & NodeBuilderFlags.UseSingleQuotesForStringLiteralType)), EmitFlags.NoAsciiEscaping)); } if (type.flags & TypeFlags.NumberLiteral) { const value = (type).value; context.approximateLength += ("" + value).length; return factory.createLiteralTypeNode(value < 0 ? factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, factory.createNumericLiteral(-value)) : factory.createNumericLiteral(value)); } if (type.flags & TypeFlags.BigIntLiteral) { context.approximateLength += (pseudoBigIntToString((type).value).length) + 1; return factory.createLiteralTypeNode((factory.createBigIntLiteral((type).value))); } if (type.flags & TypeFlags.BooleanLiteral) { context.approximateLength += (type).intrinsicName.length; return factory.createLiteralTypeNode((type).intrinsicName === "true" ? factory.createTrue() : factory.createFalse()); } if (type.flags & TypeFlags.UniqueESSymbol) { if (!(context.flags & NodeBuilderFlags.AllowUniqueESSymbolType)) { if (isValueSymbolAccessible(type.symbol, context.enclosingDeclaration)) { context.approximateLength += 6; return symbolToTypeNode(type.symbol, context, SymbolFlags.Value); } if (context.tracker.reportInaccessibleUniqueSymbolError) { context.tracker.reportInaccessibleUniqueSymbolError(); } } context.approximateLength += 13; return factory.createTypeOperatorNode(SyntaxKind.UniqueKeyword, factory.createKeywordTypeNode(SyntaxKind.SymbolKeyword)); } if (type.flags & TypeFlags.Void) { context.approximateLength += 4; return factory.createKeywordTypeNode(SyntaxKind.VoidKeyword); } if (type.flags & TypeFlags.Undefined) { context.approximateLength += 9; return factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword); } if (type.flags & TypeFlags.Null) { context.approximateLength += 4; return factory.createLiteralTypeNode(factory.createNull()); } if (type.flags & TypeFlags.Never) { context.approximateLength += 5; return factory.createKeywordTypeNode(SyntaxKind.NeverKeyword); } if (type.flags & TypeFlags.ESSymbol) { context.approximateLength += 6; return factory.createKeywordTypeNode(SyntaxKind.SymbolKeyword); } if (type.flags & TypeFlags.NonPrimitive) { context.approximateLength += 6; return factory.createKeywordTypeNode(SyntaxKind.ObjectKeyword); } if (isThisTypeParameter(type)) { if (context.flags & NodeBuilderFlags.InObjectTypeLiteral) { if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowThisInObjectLiteral)) { context.encounteredError = true; } if (context.tracker.reportInaccessibleThisError) { context.tracker.reportInaccessibleThisError(); } } context.approximateLength += 4; return factory.createThisTypeNode(); } if (!inTypeAlias && type.aliasSymbol && (context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope || isTypeSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration))) { const typeArgumentNodes = mapToTypeNodes(type.aliasTypeArguments, context); if (isReservedMemberName(type.aliasSymbol.escapedName) && !(type.aliasSymbol.flags & SymbolFlags.Class)) return factory.createTypeReferenceNode(factory.createIdentifier(""), typeArgumentNodes); return symbolToTypeNode(type.aliasSymbol, context, SymbolFlags.Type, typeArgumentNodes); } const objectFlags = getObjectFlags(type); if (objectFlags & ObjectFlags.Reference) { Debug.assert(!!(type.flags & TypeFlags.Object)); return (type).node ? visitAndTransformType(type, typeReferenceToTypeNode) : typeReferenceToTypeNode(type); } if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) { if (type.flags & TypeFlags.TypeParameter && contains(context.inferTypeParameters, type)) { context.approximateLength += (symbolName(type.symbol).length + 6); return factory.createInferTypeNode(typeParameterToDeclarationWithConstraint(type as TypeParameter, context, /*constraintNode*/ undefined)); } if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && type.flags & TypeFlags.TypeParameter && !isTypeSymbolAccessible(type.symbol, context.enclosingDeclaration)) { const name = typeParameterToName(type, context); context.approximateLength += idText(name).length; return factory.createTypeReferenceNode(factory.createIdentifier(idText(name)), /*typeArguments*/ undefined); } // Ignore constraint/default when creating a usage (as opposed to declaration) of a type parameter. return type.symbol ? symbolToTypeNode(type.symbol, context, SymbolFlags.Type) : factory.createTypeReferenceNode(factory.createIdentifier("?"), /*typeArguments*/ undefined); } if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) { const types = type.flags & TypeFlags.Union ? formatUnionTypes((type).types) : (type).types; if (length(types) === 1) { return typeToTypeNodeHelper(types[0], context); } const typeNodes = mapToTypeNodes(types, context, /*isBareList*/ true); if (typeNodes && typeNodes.length > 0) { const unionOrIntersectionTypeNode = type.flags & TypeFlags.Union ? factory.createUnionTypeNode(typeNodes) : factory.createIntersectionTypeNode(typeNodes); return unionOrIntersectionTypeNode; } 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); } if (type.flags & TypeFlags.Index) { const indexedType = (type).type; context.approximateLength += 6; const indexTypeNode = typeToTypeNodeHelper(indexedType, context); return factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, indexTypeNode); } if (type.flags & TypeFlags.TemplateLiteral) { const texts = (type).texts; const types = (type).types; const templateHead = factory.createTemplateHead(texts[0]); const templateSpans = factory.createNodeArray( map(types, (t, i) => factory.createTemplateLiteralTypeSpan( typeToTypeNodeHelper(t, context), (i < types.length - 1 ? factory.createTemplateMiddle : factory.createTemplateTail)(texts[i + 1])))); context.approximateLength += 2; return factory.createTemplateLiteralType(templateHead, templateSpans); } if (type.flags & TypeFlags.StringMapping) { const typeNode = typeToTypeNodeHelper((type).type, context); return symbolToTypeNode((type).symbol, context, SymbolFlags.Type, [typeNode]); } if (type.flags & TypeFlags.IndexedAccess) { const objectTypeNode = typeToTypeNodeHelper((type).objectType, context); const indexTypeNode = typeToTypeNodeHelper((type).indexType, context); context.approximateLength += 2; return factory.createIndexedAccessTypeNode(objectTypeNode, indexTypeNode); } if (type.flags & TypeFlags.Conditional) { const checkTypeNode = typeToTypeNodeHelper((type).checkType, context); 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)); context.approximateLength += 15; return factory.createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode); } if (type.flags & TypeFlags.Substitution) { return typeToTypeNodeHelper((type).baseType, context); } return Debug.fail("Should be unreachable."); 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 createMappedTypeNodeFromType(type: MappedType) { Debug.assert(!!(type.flags & TypeFlags.Object)); const readonlyToken = type.declaration.readonlyToken ? factory.createToken(type.declaration.readonlyToken.kind) : undefined; const questionToken = type.declaration.questionToken ? factory.createToken(type.declaration.questionToken.kind) : undefined; let appropriateConstraintTypeNode: TypeNode; 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` appropriateConstraintTypeNode = factory.createTypeOperatorNode(SyntaxKind.KeyOfKeyword, typeToTypeNodeHelper(getModifiersTypeFromMappedType(type), context)); } else { appropriateConstraintTypeNode = typeToTypeNodeHelper(getConstraintTypeFromMappedType(type), context); } const typeParameterNode = typeParameterToDeclarationWithConstraint(getTypeParameterFromMappedType(type), context, appropriateConstraintTypeNode); const nameTypeNode = type.declaration.nameType ? typeToTypeNodeHelper(getNameTypeFromMappedType(type)!, context) : undefined; const templateTypeNode = typeToTypeNodeHelper(getTemplateTypeFromMappedType(type), context); const mappedTypeNode = factory.createMappedTypeNode(readonlyToken, typeParameterNode, nameTypeNode, questionToken, templateTypeNode); context.approximateLength += 10; return setEmitFlags(mappedTypeNode, EmitFlags.SingleLine); } function createAnonymousTypeNode(type: ObjectType): TypeNode { const typeId = type.id; const symbol = type.symbol; if (symbol) { 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 (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) && !(symbol.valueDeclaration.kind === SyntaxKind.ClassExpression && context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) || symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) || shouldWriteTypeOfFunctionSymbol()) { return symbolToTypeNode(symbol, context, isInstanceType); } else 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 => hasSyntacticModifier(declaration, ModifierFlags.Static)); 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: Type, transform: (type: Type) => T) { const typeId = type.id; const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; const id = getObjectFlags(type) & ObjectFlags.Reference && (type).node ? "N" + getNodeId((type).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(); } 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 result = transform(type); context.visitedTypes.delete(typeId); if (id) { context.symbolDepth!.set(id, depth!); } return result; } 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.stringIndexInfo && !resolved.numberIndexInfo) { 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); return signatureNode; } if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) { const signature = resolved.constructSignatures[0]; const signatureNode = signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructorType, context); return signatureNode; } } const savedFlags = context.flags; context.flags |= NodeBuilderFlags.InObjectTypeLiteral; const members = createTypeNodesFromResolvedType(resolved); context.flags = savedFlags; const typeLiteralNode = factory.createTypeLiteralNode(members); context.approximateLength += 2; return setEmitFlags(typeLiteralNode, (context.flags & NodeBuilderFlags.MultilineObjectLiterals) ? 0 : EmitFlags.SingleLine); } function typeReferenceToTypeNode(type: TypeReference) { const 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) { if (typeArguments.length > 0) { const arity = getTypeReferenceArity(type); const tupleConstituentNodes = mapToTypeNodes(typeArguments.slice(0, arity), context); if (tupleConstituentNodes) { if ((type.target as TupleType).labeledElementDeclarations) { for (let i = 0; i < tupleConstituentNodes.length; i++) { const flags = (type.target as TupleType).elementFlags[i]; tupleConstituentNodes[i] = factory.createNamedTupleMember( flags & ElementFlags.Variable ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined, factory.createIdentifier(unescapeLeadingUnderscores(getTupleElementLabel((type.target as TupleType).labeledElementDeclarations![i]))), flags & ElementFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) : tupleConstituentNodes[i] ); } } else { for (let i = 0; i < Math.min(arity, tupleConstituentNodes.length); i++) { const flags = (type.target as TupleType).elementFlags[i]; tupleConstituentNodes[i] = flags & ElementFlags.Variable ? factory.createRestTypeNode(flags & ElementFlags.Rest ? factory.createArrayTypeNode(tupleConstituentNodes[i]) : tupleConstituentNodes[i]) : flags & ElementFlags.Optional ? factory.createOptionalTypeNode(tupleConstituentNodes[i]) : tupleConstituentNodes[i]; } } const tupleTypeNode = setEmitFlags(factory.createTupleTypeNode(tupleConstituentNodes), EmitFlags.SingleLine); return (type.target).readonly ? factory.createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode; } } if (context.encounteredError || (context.flags & NodeBuilderFlags.AllowEmptyTuple)) { const tupleTypeNode = setEmitFlags(factory.createTupleTypeNode([]), EmitFlags.SingleLine); return (type.target).readonly ? factory.createTypeOperatorNode(SyntaxKind.ReadonlyKeyword, tupleTypeNode) : tupleTypeNode; } context.encounteredError = true; return undefined!; // TODO: GH#18217 } else if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral && type.symbol.valueDeclaration && isClassLike(type.symbol.valueDeclaration) && !isValueSymbolAccessible(type.symbol, context.enclosingDeclaration) ) { return createAnonymousTypeNode(type); } else { const outerTypeParameters = type.target.outerTypeParameters; let i = 0; let resultType: TypeReferenceNode | ImportTypeNode | undefined; if (outerTypeParameters) { const length = outerTypeParameters.length; while (i < length) { // Find group of type arguments for type parameters with the same declaring container. const start = i; const parent = getParentSymbolOfTypeParameter(outerTypeParameters[i])!; do { i++; } while (i < length && getParentSymbolOfTypeParameter(outerTypeParameters[i]) === parent); // When type parameters are their own type arguments for the whole group (i.e. we have // the default outer type arguments), we don't show the group. if (!rangeEquals(outerTypeParameters, typeArguments, start, i)) { const typeArgumentSlice = mapToTypeNodes(typeArguments.slice(start, i), context); const flags = context.flags; context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences; const ref = symbolToTypeNode(parent, context, SymbolFlags.Type, typeArgumentSlice) as TypeReferenceNode | ImportTypeNode; context.flags = flags; resultType = !resultType ? ref : appendReferenceToType(resultType, ref as TypeReferenceNode); } } } let typeArgumentNodes: readonly TypeNode[] | undefined; if (typeArguments.length > 0) { const typeParameterCount = (type.target.typeParameters || emptyArray).length; typeArgumentNodes = mapToTypeNodes(typeArguments.slice(i, typeParameterCount), context); } const flags = context.flags; context.flags |= NodeBuilderFlags.ForbidIndexedAccessSymbolReferences; const finalRef = symbolToTypeNode(type.symbol, context, SymbolFlags.Type, typeArgumentNodes); context.flags = flags; 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)) { qualifier = factory.updateIdentifier(qualifier, typeArguments); } else { qualifier = factory.updateQualifiedName(qualifier, qualifier.left, factory.updateIdentifier(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, qualifier, typeArguments, root.isTypeOf); } else { // first shift type arguments let typeArguments = root.typeArguments; let typeName = root.typeName; if (isIdentifier(typeName)) { typeName = factory.updateIdentifier(typeName, typeArguments); } else { typeName = factory.updateQualifiedName(typeName, typeName.left, factory.updateIdentifier(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 createTypeNodesFromResolvedType(resolvedType: ResolvedType): TypeElement[] | undefined { if (checkTruncationLength(context)) { return [factory.createPropertySignature(/*modifiers*/ undefined, "...", /*questionToken*/ undefined, /*type*/ undefined)]; } const typeElements: TypeElement[] = []; for (const signature of resolvedType.callSignatures) { typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.CallSignature, context)); } for (const signature of resolvedType.constructSignatures) { typeElements.push(signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, context)); } if (resolvedType.stringIndexInfo) { let indexSignature: IndexSignatureDeclaration; if (resolvedType.objectFlags & ObjectFlags.ReverseMapped) { indexSignature = indexInfoToIndexSignatureDeclarationHelper( createIndexInfo(anyType, resolvedType.stringIndexInfo.isReadonly, resolvedType.stringIndexInfo.declaration), IndexKind.String, context, createElidedInformationPlaceholder(context)); } else { indexSignature = indexInfoToIndexSignatureDeclarationHelper(resolvedType.stringIndexInfo, IndexKind.String, context, /*typeNode*/ undefined); } typeElements.push(indexSignature); } if (resolvedType.numberIndexInfo) { typeElements.push(indexInfoToIndexSignatureDeclarationHelper(resolvedType.numberIndexInfo, IndexKind.Number, context, /*typeNode*/ undefined)); } const properties = resolvedType.properties; if (!properties) { return typeElements; } let i = 0; for (const propertySymbol of properties) { 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 (checkTruncationLength(context) && (i + 2 < properties.length - 1)) { typeElements.push(factory.createPropertySignature(/*modifiers*/ undefined, `... ${properties.length - i} more ...`, /*questionToken*/ undefined, /*type*/ undefined)); addPropertyToElementList(properties[properties.length - 1], context, typeElements); break; } addPropertyToElementList(propertySymbol, context, typeElements); } return typeElements.length ? typeElements : undefined; } } function createElidedInformationPlaceholder(context: NodeBuilderContext) { context.approximateLength += 3; if (!(context.flags & NodeBuilderFlags.NoTruncation)) { return factory.createTypeReferenceNode(factory.createIdentifier("..."), /*typeArguments*/ undefined); } return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); } function addPropertyToElementList(propertySymbol: Symbol, context: NodeBuilderContext, typeElements: TypeElement[]) { const propertyIsReverseMapped = !!(getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped); const propertyType = propertyIsReverseMapped && context.flags & NodeBuilderFlags.InReverseMappedType ? anyType : getTypeOfSymbol(propertySymbol); const saveEnclosingDeclaration = context.enclosingDeclaration; context.enclosingDeclaration = undefined; if (context.tracker.trackSymbol && getCheckFlags(propertySymbol) & CheckFlags.Late) { const decl = first(propertySymbol.declarations); if (hasLateBindableName(decl)) { if (isBinaryExpression(decl)) { const name = getNameOfDeclaration(decl); if (name && isElementAccessExpression(name) && isPropertyAccessEntityNameExpression(name.argumentExpression)) { trackComputedName(name.argumentExpression, saveEnclosingDeclaration, context); } } else { trackComputedName(decl.name.expression, saveEnclosingDeclaration, context); } } } context.enclosingDeclaration = saveEnclosingDeclaration; const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context); context.approximateLength += (symbolName(propertySymbol).length + 1); 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 }); typeElements.push(preserveCommentsOn(methodDeclaration)); } } else { const savedFlags = context.flags; context.flags |= propertyIsReverseMapped ? NodeBuilderFlags.InReverseMappedType : 0; let propertyTypeNode: TypeNode; if (propertyIsReverseMapped && !!(savedFlags & NodeBuilderFlags.InReverseMappedType)) { propertyTypeNode = createElidedInformationPlaceholder(context); } else { propertyTypeNode = propertyType ? serializeTypeForDeclaration(context, propertyType, propertySymbol, saveEnclosingDeclaration) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); } context.flags = savedFlags; 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)); } function preserveCommentsOn(node: T) { if (some(propertySymbol.declarations, d => d.kind === SyntaxKind.JSDocPropertyTag)) { const d = find(propertySymbol.declarations, d => d.kind === SyntaxKind.JSDocPropertyTag)! as JSDocPropertyTag; const commentText = d.comment; if (commentText) { setSyntheticLeadingComments(node, [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }]); } } else if (propertySymbol.valueDeclaration) { // Copy comments to node for declaration emit setCommentRange(node, propertySymbol.valueDeclaration); } return node; } } function mapToTypeNodes(types: readonly Type[] | undefined, context: NodeBuilderContext, isBareList?: boolean): TypeNode[] | undefined { if (some(types)) { if (checkTruncationLength(context)) { if (!isBareList) { return [factory.createTypeReferenceNode("...", /*typeArguments*/ undefined)]; } else if (types.length > 2) { return [ typeToTypeNodeHelper(types[0], context), 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 ? createUnderscoreEscapedMultiMap<[Type, number]>() : undefined; const result: TypeNode[] = []; let i = 0; for (const type of types) { i++; if (checkTruncationLength(context) && (i + 2 < types.length - 1)) { result.push(factory.createTypeReferenceNode(`... ${types.length - i} more ...`, /*typeArguments*/ undefined)); const typeNode = typeToTypeNodeHelper(types[types.length - 1], context); if (typeNode) { result.push(typeNode); } break; } context.approximateLength += 2; // Account for whitespace + separator const typeNode = typeToTypeNodeHelper(type, context); if (typeNode) { result.push(typeNode); if (seenNames && isIdentifierTypeReference(typeNode)) { seenNames.add(typeNode.typeName.escapedText, [type, result.length - 1]); } } } if (seenNames) { // To avoid printing types like `[Foo, Foo]` or `Bar & Bar` where // occurrences of the same name actually come from different // namespaces, go through the single-identifier type reference nodes // we just generated, and see if any names were generated more than // once while referring to different types. If so, regenerate the // type node for each entry by that name with the // `UseFullyQualifiedType` flag enabled. const saveContextFlags = context.flags; context.flags |= NodeBuilderFlags.UseFullyQualifiedType; seenNames.forEach(types => { if (!arrayIsHomogeneous(types, ([a], [b]) => typesAreSameReference(a, b))) { for (const [type, resultIndex] of types) { result[resultIndex] = typeToTypeNodeHelper(type, context); } } }); context.flags = saveContextFlags; } 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, kind: IndexKind, context: NodeBuilderContext, typeNode: TypeNode | undefined): IndexSignatureDeclaration { const name = getNameFromIndexInfo(indexInfo) || "x"; const indexerTypeNode = factory.createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword); const indexingParameter = factory.createParameterDeclaration( /*decorators*/ undefined, /*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( /*decorators*/ undefined, indexInfo.isReadonly ? [factory.createToken(SyntaxKind.ReadonlyKeyword)] : undefined, [indexingParameter], typeNode); } interface SignatureToSignatureDeclarationOptions { modifiers?: readonly Modifier[]; name?: PropertyName; questionToken?: QuestionToken; privateSymbolVisitor?: (s: Symbol) => void; bundledImports?: boolean; } function signatureToSignatureDeclarationHelper(signature: Signature, kind: SignatureDeclaration["kind"], context: NodeBuilderContext, options?: SignatureToSignatureDeclarationOptions): SignatureDeclaration { const suppressAny = context.flags & NodeBuilderFlags.SuppressAnyReturnType; if (suppressAny) context.flags &= ~NodeBuilderFlags.SuppressAnyReturnType; // suppress only toplevel `any`s let typeParameters: TypeParameterDeclaration[] | undefined; let typeArguments: TypeNode[] | undefined; 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 expandedParams = getExpandedParameters(signature, /*skipUnionExpanding*/ true)[0]; // 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, options?.privateSymbolVisitor, options?.bundledImports)); if (signature.thisParameter) { const thisParameter = symbolToParameterDeclaration(signature.thisParameter, context); parameters.unshift(thisParameter); } let returnTypeNode: TypeNode | undefined; const typePredicate = getTypePredicateOfSignature(signature); if (typePredicate) { 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); returnTypeNode = factory.createTypePredicateNode(assertsModifier, parameterName, typeNode); } else { const returnType = getReturnTypeOfSignature(signature); if (returnType && !(suppressAny && isTypeAny(returnType))) { returnTypeNode = serializeReturnTypeForSignature(context, returnType, signature, options?.privateSymbolVisitor, options?.bundledImports); } else if (!suppressAny) { returnTypeNode = factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); } } context.approximateLength += 3; // Usually a signature contributes a few more characters than this, but 3 is the minimum const node = kind === SyntaxKind.CallSignature ? factory.createCallSignature(typeParameters, parameters, returnTypeNode) : kind === SyntaxKind.ConstructSignature ? factory.createConstructSignature(typeParameters, parameters, returnTypeNode) : kind === SyntaxKind.MethodSignature ? factory.createMethodSignature(options?.modifiers, options?.name ?? factory.createIdentifier(""), options?.questionToken, typeParameters, parameters, returnTypeNode) : kind === SyntaxKind.MethodDeclaration ? factory.createMethodDeclaration(/*decorators*/ undefined, options?.modifiers, /*asteriskToken*/ undefined, options?.name ?? factory.createIdentifier(""), /*questionToken*/ undefined, typeParameters, parameters, returnTypeNode, /*body*/ undefined) : kind === SyntaxKind.Constructor ? factory.createConstructorDeclaration(/*decorators*/ undefined, options?.modifiers, parameters, /*body*/ undefined) : kind === SyntaxKind.GetAccessor ? factory.createGetAccessorDeclaration(/*decorators*/ undefined, options?.modifiers, options?.name ?? factory.createIdentifier(""), parameters, returnTypeNode, /*body*/ undefined) : kind === SyntaxKind.SetAccessor ? factory.createSetAccessorDeclaration(/*decorators*/ undefined, options?.modifiers, options?.name ?? factory.createIdentifier(""), parameters, /*body*/ undefined) : kind === SyntaxKind.IndexSignature ? factory.createIndexSignature(/*decorators*/ undefined, options?.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(typeParameters, parameters, returnTypeNode ?? factory.createTypeReferenceNode(factory.createIdentifier(""))) : kind === SyntaxKind.FunctionDeclaration ? factory.createFunctionDeclaration(/*decorators*/ undefined, options?.modifiers, /*asteriskToken*/ undefined, options?.name ? cast(options.name, isIdentifier) : factory.createIdentifier(""), typeParameters, parameters, returnTypeNode, /*body*/ undefined) : kind === SyntaxKind.FunctionExpression ? factory.createFunctionExpression(options?.modifiers, /*asteriskToken*/ undefined, options?.name ? cast(options.name, isIdentifier) : factory.createIdentifier(""), typeParameters, parameters, returnTypeNode, factory.createBlock([])) : kind === SyntaxKind.ArrowFunction ? factory.createArrowFunction(options?.modifiers, typeParameters, parameters, returnTypeNode, /*equalsGreaterThanToken*/ undefined, factory.createBlock([])) : Debug.assertNever(kind); if (typeArguments) { node.typeArguments = factory.createNodeArray(typeArguments); } return node; } function typeParameterToDeclarationWithConstraint(type: TypeParameter, context: NodeBuilderContext, constraintNode: TypeNode | undefined): TypeParameterDeclaration { const savedContextFlags = context.flags; context.flags &= ~NodeBuilderFlags.WriteTypeParametersInQualifiedName; // Avoids potential infinite loop when building for a claimspace with a generic const name = typeParameterToName(type, context); const defaultParameter = getDefaultFromTypeParameter(type); const defaultParameterNode = defaultParameter && typeToTypeNodeHelper(defaultParameter, context); context.flags = savedContextFlags; return factory.createTypeParameterDeclaration(name, constraintNode, defaultParameterNode); } function typeParameterToDeclaration(type: TypeParameter, context: NodeBuilderContext, constraint = getConstraintOfTypeParameter(type)): TypeParameterDeclaration { const constraintNode = constraint && typeToTypeNodeHelper(constraint, context); return typeParameterToDeclarationWithConstraint(type, context, constraintNode); } function symbolToParameterDeclaration(parameterSymbol: Symbol, context: NodeBuilderContext, preserveModifierFlags?: boolean, privateSymbolVisitor?: (s: Symbol) => void, bundledImports?: boolean): ParameterDeclaration { let parameterDeclaration: ParameterDeclaration | JSDocParameterTag | undefined = getDeclarationOfKind(parameterSymbol, SyntaxKind.Parameter); if (!parameterDeclaration && !isTransientSymbol(parameterSymbol)) { parameterDeclaration = getDeclarationOfKind(parameterSymbol, SyntaxKind.JSDocParameterTag); } let parameterType = getTypeOfSymbol(parameterSymbol); if (parameterDeclaration && isRequiredInitializedParameter(parameterDeclaration)) { parameterType = getOptionalType(parameterType); } if ((context.flags & NodeBuilderFlags.NoUndefinedOptionalParameterType) && parameterDeclaration && !isJSDocParameterTag(parameterDeclaration) && isOptionalUninitializedParameter(parameterDeclaration)) { parameterType = getTypeWithFacts(parameterType, TypeFacts.NEUndefined); } const parameterTypeNode = serializeTypeForDeclaration(context, parameterType, parameterSymbol, context.enclosingDeclaration, privateSymbolVisitor, bundledImports); const modifiers = !(context.flags & NodeBuilderFlags.OmitParameterModifiers) && preserveModifierFlags && parameterDeclaration && parameterDeclaration.modifiers ? parameterDeclaration.modifiers.map(factory.cloneNode) : undefined; const isRest = parameterDeclaration && isRestParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.RestParameter; const dotDotDotToken = isRest ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined; const name = 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); const isOptional = parameterDeclaration && isOptionalParameter(parameterDeclaration) || getCheckFlags(parameterSymbol) & CheckFlags.OptionalParameter; const questionToken = isOptional ? factory.createToken(SyntaxKind.QuestionToken) : undefined; const parameterNode = factory.createParameterDeclaration( /*decorators*/ undefined, modifiers, dotDotDotToken, name, questionToken, parameterTypeNode, /*initializer*/ undefined); context.approximateLength += symbolName(parameterSymbol).length + 3; return parameterNode; function cloneBindingName(node: BindingName): BindingName { return elideInitializerAndSetEmitFlags(node); function elideInitializerAndSetEmitFlags(node: Node): Node { if (context.tracker.trackSymbol && isComputedPropertyName(node) && isLateBindableName(node)) { trackComputedName(node.expression, context.enclosingDeclaration, context); } let visited = visitEachChild(node, elideInitializerAndSetEmitFlags, nullTransformationContext, /*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.trackSymbol) return; // get symbol of the first identifier of the entityName const firstIdentifier = getFirstIdentifier(accessExpression); const name = resolveName(firstIdentifier, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); if (name) { context.tracker.trackSymbol(name, enclosingDeclaration, SymbolFlags.Value); } } function lookupSymbolChain(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags, yieldModuleSymbol?: boolean) { context.tracker.trackSymbol!(symbol, context.enclosingDeclaration, meaning); // TODO: GH#18217 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.flags & NodeBuilderFlags.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 <= index && index < chain.length); const symbol = chain[index]; const symbolId = getSymbolId(symbol); if (context.typeParameterSymbolList?.has(symbolId)) { return undefined; } (context.typeParameterSymbolList || (context.typeParameterSymbolList = new Set())).add(symbolId); let typeParameterNodes: readonly TypeNode[] | readonly TypeParameterDeclaration[] | undefined; if (context.flags & NodeBuilderFlags.WriteTypeParametersInQualifiedName && index < (chain.length - 1)) { const parentSymbol = symbol; const nextSymbol = chain[index + 1]; if (getCheckFlags(nextSymbol) & CheckFlags.Instantiated) { const params = getTypeParametersOfClassOrInterface( parentSymbol.flags & SymbolFlags.Alias ? resolveAlias(parentSymbol) : parentSymbol ); typeParameterNodes = mapToTypeNodes(map(params, t => getMappedType(t, (nextSymbol as TransientSymbol).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) { 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 (context.tracker.trackReferencedAmbientModule) { const ambientDecls = filter(symbol.declarations, isAmbientModule); if (length(ambientDecls)) { for (const decl of ambientDecls) { context.tracker.trackReferencedAmbientModule(decl, symbol); } } } if (ambientModuleSymbolRegex.test(symbol.escapedName as string)) { return (symbol.escapedName as string).substring(1, (symbol.escapedName as string).length - 1); } } if (!context.enclosingDeclaration || !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 contextFile = getSourceFileOfNode(getOriginalNode(context.enclosingDeclaration)); const links = getSymbolLinks(symbol); let specifier = links.specifierCache && links.specifierCache.get(contextFile.path); if (!specifier) { const isBundle = !!outFile(compilerOptions); // 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, specifierCompilerOptions, contextFile, moduleResolverHost, { importModuleSpecifierPreference: isBundle ? "non-relative" : "relative", importModuleSpecifierEnding: isBundle ? "minimal" : undefined }, )); links.specifierCache ??= new Map(); links.specifierCache.set(contextFile.path, specifier); } return specifier; } 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 specifier = getSpecifierForModuleSymbol(chain[0], context); if (!(context.flags & NodeBuilderFlags.AllowNodeModulesRelativePaths) && getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs && specifier.indexOf("/node_modules/") >= 0) { // 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(specifier); } } const lit = factory.createLiteralTypeNode(factory.createStringLiteral(specifier)); if (context.tracker.trackExternalModuleSymbolOfImportTypeNode) context.tracker.trackExternalModuleSymbolOfImportTypeNode(chain[0]); context.approximateLength += specifier.length + 10; // specifier + import("") if (!nonRootParts || isEntityName(nonRootParts)) { if (nonRootParts) { const lastId = isIdentifier(nonRootParts) ? nonRootParts : nonRootParts.right; lastId.typeArguments = undefined; } return factory.createImportTypeNode(lit, 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, 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 = lastId.typeArguments; 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) { 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, typeParameterNodes), EmitFlags.NoAsciiEscaping); 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 typeParameterShadowsNameInScope(escapedName: __String, context: NodeBuilderContext, type: TypeParameter) { const result = resolveName(context.enclosingDeclaration, escapedName, SymbolFlags.Type, /*nameNotFoundArg*/ undefined, escapedName, /*isUse*/ false); if (result) { if (result.flags & SymbolFlags.TypeParameter && result === type.symbol) { return false; } return true; } 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)"); } if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) { const rawtext = result.escapedText as string; let i = 0; let text = rawtext; while (context.typeParameterNamesByText?.has(text) || typeParameterShadowsNameInScope(text as __String, context, type)) { i++; text = `${rawtext}_${i}`; } if (text !== rawtext) { result = factory.createIdentifier(text, result.typeArguments); } (context.typeParameterNames || (context.typeParameterNames = new Map())).set(getTypeId(type), result); (context.typeParameterNamesByText || (context.typeParameterNamesByText = new Set())).add(result.escapedText as string); } 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.AllowQualifedNameInPlaceOfIdentifier)) { 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, typeParameterNodes), EmitFlags.NoAsciiEscaping); 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)) { return factory.createStringLiteral(getSpecifierForModuleSymbol(symbol, context)); } const canUsePropertyAccess = firstChar === CharacterCodes.hash ? symbolName.length > 1 && isIdentifierStart(symbolName.charCodeAt(1), languageVersion) : isIdentifierStart(firstChar, languageVersion); if (index === 0 || canUsePropertyAccess) { const identifier = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); identifier.symbol = symbol; 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)) { expression = factory.createStringLiteral( symbolName .substring(1, symbolName.length - 1) .replace(/\\./g, s => s.substring(1)), firstChar === CharacterCodes.singleQuote); } else if (("" + +symbolName) === symbolName) { expression = factory.createNumericLiteral(+symbolName); } if (!expression) { expression = setEmitFlags(factory.createIdentifier(symbolName, typeParameterNodes), EmitFlags.NoAsciiEscaping); expression.symbol = symbol; } return factory.createElementAccessExpression(createExpressionFromSymbolChain(chain, index - 1), expression); } } } function isStringNamed(d: Declaration) { const name = getNameOfDeclaration(d); return !!name && 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 singleQuote = !!length(symbol.declarations) && every(symbol.declarations, isSingleQuotedStringNamed); const fromNameType = getPropertyNameNodeForSymbolFromNameType(symbol, context, singleQuote); if (fromNameType) { return fromNameType; } if (isKnownSymbol(symbol)) { return factory.createComputedPropertyName(factory.createPropertyAccessExpression(factory.createIdentifier("Symbol"), (symbol.escapedName as string).substr(3))); } const rawName = unescapeLeadingUnderscores(symbol.escapedName); const stringNamed = !!length(symbol.declarations) && every(symbol.declarations, isStringNamed); return createPropertyNameNodeForIdentifierOrLiteral(rawName, stringNamed, singleQuote); } // See getNameForSymbolFromNameType for a stringy equivalent function getPropertyNameNodeForSymbolFromNameType(symbol: Symbol, context: NodeBuilderContext, singleQuote?: boolean) { const nameType = getSymbolLinks(symbol).nameType; if (nameType) { if (nameType.flags & TypeFlags.StringOrNumberLiteral) { const name = "" + (nameType).value; if (!isIdentifierText(name, compilerOptions.target) && !isNumericLiteralName(name)) { return factory.createStringLiteral(name, !!singleQuote); } if (isNumericLiteralName(name) && startsWith(name, "-")) { return factory.createComputedPropertyName(factory.createNumericLiteral(+name)); } return createPropertyNameNodeForIdentifierOrLiteral(name); } if (nameType.flags & TypeFlags.UniqueESSymbol) { return factory.createComputedPropertyName(symbolToExpression((nameType).symbol, context, SymbolFlags.Value)); } } } function createPropertyNameNodeForIdentifierOrLiteral(name: string, stringNamed?: boolean, singleQuote?: boolean) { return isIdentifierText(name, compilerOptions.target) ? factory.createIdentifier(name) : !stringNamed && isNumericLiteralName(name) && +name >= 0 ? factory.createNumericLiteral(+name) : factory.createStringLiteral(name, !!singleQuote); } function cloneNodeBuilderContext(context: NodeBuilderContext): NodeBuilderContext { const initial: NodeBuilderContext = { ...context }; // 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 if (initial.typeParameterNames) { initial.typeParameterNames = new Map(initial.typeParameterNames); } if (initial.typeParameterNamesByText) { initial.typeParameterNamesByText = new Set(initial.typeParameterNamesByText); } if (initial.typeParameterSymbolList) { initial.typeParameterSymbolList = new Set(initial.typeParameterSymbolList); } return initial; } function getDeclarationWithTypeAnnotation(symbol: Symbol, enclosingDeclaration: Node | undefined) { return symbol.declarations && find(symbol.declarations, s => !!getEffectiveTypeAnnotationNode(s) && (!enclosingDeclaration || !!findAncestor(s, n => n === enclosingDeclaration))); } function existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing: TypeNode, type: Type) { return !(getObjectFlags(type) & ObjectFlags.Reference) || !isTypeReferenceNode(existing) || length(existing.typeArguments) >= getMinTypeArgumentCount((type as TypeReference).target.typeParameters); } /** * 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 serializeTypeForDeclaration(context: NodeBuilderContext, type: Type, symbol: Symbol, enclosingDeclaration: Node | undefined, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { if (type !== errorType && enclosingDeclaration) { const declWithExistingAnnotation = getDeclarationWithTypeAnnotation(symbol, enclosingDeclaration); if (declWithExistingAnnotation && !isFunctionLikeDeclaration(declWithExistingAnnotation)) { // try to reuse the existing annotation const existing = getEffectiveTypeAnnotationNode(declWithExistingAnnotation)!; if (getTypeFromTypeNode(existing) === type && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(existing, type)) { const result = serializeExistingTypeNode(context, existing, includePrivateSymbol, bundled); if (result) { return result; } } } } const oldFlags = context.flags; if (type.flags & TypeFlags.UniqueESSymbol && type.symbol === symbol && (!context.enclosingDeclaration || some(symbol.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(context.enclosingDeclaration!)))) { context.flags |= NodeBuilderFlags.AllowUniqueESSymbolType; } const result = typeToTypeNodeHelper(type, context); context.flags = oldFlags; return result; } function serializeReturnTypeForSignature(context: NodeBuilderContext, type: Type, signature: Signature, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { if (type !== errorType && context.enclosingDeclaration) { const annotation = signature.declaration && getEffectiveReturnTypeNode(signature.declaration); if (!!findAncestor(annotation, n => n === context.enclosingDeclaration) && annotation && instantiateType(getTypeFromTypeNode(annotation), signature.mapper) === type && existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(annotation, type)) { const result = serializeExistingTypeNode(context, annotation, includePrivateSymbol, bundled); if (result) { return result; } } } return typeToTypeNodeHelper(type, context); } function serializeExistingTypeNode(context: NodeBuilderContext, existing: TypeNode, includePrivateSymbol?: (s: Symbol) => void, bundled?: boolean) { if (cancellationToken && cancellationToken.throwIfCancellationRequested) { cancellationToken.throwIfCancellationRequested(); } let hadError = false; const file = getSourceFileOfNode(existing); const transformed = visitNode(existing, visitExistingNodeTreeSymbols); if (hadError) { return undefined; } return transformed === existing ? setTextRange(factory.cloneNode(existing), existing) : transformed; function visitExistingNodeTreeSymbols(node: T): Node { // We don't _actually_ support jsdoc namepath types, emit `any` instead if (isJSDocAllType(node) || node.kind === SyntaxKind.JSDocNamepathType) { return factory.createKeywordTypeNode(SyntaxKind.AnyKeyword); } if (isJSDocUnknownType(node)) { return factory.createKeywordTypeNode(SyntaxKind.UnknownKeyword); } if (isJSDocNullableType(node)) { return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols), factory.createLiteralTypeNode(factory.createNull())]); } if (isJSDocOptionalType(node)) { return factory.createUnionTypeNode([visitNode(node.type, visitExistingNodeTreeSymbols), factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]); } if (isJSDocNonNullableType(node)) { return visitNode(node.type, visitExistingNodeTreeSymbols); } if (isJSDocVariadicType(node)) { return factory.createArrayTypeNode(visitNode((node as JSDocVariadicType).type, visitExistingNodeTreeSymbols)); } if (isJSDocTypeLiteral(node)) { return factory.createTypeLiteralNode(map(node.jsDocPropertyTags, t => { const name = isIdentifier(t.name) ? t.name : t.name.right; const typeViaParent = getTypeOfPropertyOfType(getTypeFromTypeNode(node), name.escapedText); const overrideTypeNode = typeViaParent && t.typeExpression && getTypeFromTypeNode(t.typeExpression.type) !== typeViaParent ? typeToTypeNodeHelper(typeViaParent, context) : undefined; return factory.createPropertySignature( /*modifiers*/ undefined, name, t.typeExpression && isJSDocOptionalType(t.typeExpression.type) ? factory.createToken(SyntaxKind.QuestionToken) : undefined, overrideTypeNode || (t.typeExpression && visitNode(t.typeExpression.type, visitExistingNodeTreeSymbols)) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) ); })); } if (isTypeReferenceNode(node) && isIdentifier(node.typeName) && node.typeName.escapedText === "") { return setOriginalNode(factory.createKeywordTypeNode(SyntaxKind.AnyKeyword), node); } if ((isExpressionWithTypeArguments(node) || isTypeReferenceNode(node)) && isJSDocIndexSignature(node)) { return factory.createTypeLiteralNode([factory.createIndexSignature( /*decorators*/ undefined, /*modifiers*/ undefined, [factory.createParameterDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, /*dotdotdotToken*/ undefined, "x", /*questionToken*/ undefined, visitNode(node.typeArguments![0], visitExistingNodeTreeSymbols) )], visitNode(node.typeArguments![1], visitExistingNodeTreeSymbols) )]); } if (isJSDocFunctionType(node)) { if (isJSDocConstructSignature(node)) { let newTypeNode: TypeNode | undefined; return factory.createConstructorTypeNode( visitNodes(node.typeParameters, visitExistingNodeTreeSymbols), mapDefined(node.parameters, (p, i) => p.name && isIdentifier(p.name) && p.name.escapedText === "new" ? (newTypeNode = p.type, undefined) : factory.createParameterDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, getEffectiveDotDotDotForParameter(p), getNameForJSDocFunctionParameter(p, i), p.questionToken, visitNode(p.type, visitExistingNodeTreeSymbols), /*initializer*/ undefined )), visitNode(newTypeNode || node.type, visitExistingNodeTreeSymbols) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) ); } else { return factory.createFunctionTypeNode( visitNodes(node.typeParameters, visitExistingNodeTreeSymbols), map(node.parameters, (p, i) => factory.createParameterDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, getEffectiveDotDotDotForParameter(p), getNameForJSDocFunctionParameter(p, i), p.questionToken, visitNode(p.type, visitExistingNodeTreeSymbols), /*initializer*/ undefined )), visitNode(node.type, visitExistingNodeTreeSymbols) || factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) ); } } if (isTypeReferenceNode(node) && isInJSDoc(node) && (!existingTypeNodeIsNotReferenceOrIsReferenceWithCompatibleTypeArgumentCount(node, getTypeFromTypeNode(node)) || getIntendedTypeFromJSDocTypeReference(node) || unknownSymbol === resolveTypeReferenceName(getTypeReferenceName(node), SymbolFlags.Type, /*ignoreErrors*/ true))) { return setOriginalNode(typeToTypeNodeHelper(getTypeFromTypeNode(node), context), node); } if (isLiteralImportTypeNode(node)) { return factory.updateImportTypeNode( node, factory.updateLiteralTypeNode(node.argument, rewriteModuleSpecifier(node, node.argument.literal)), node.qualifier, visitNodes(node.typeArguments, visitExistingNodeTreeSymbols, isTypeNode), node.isTypeOf ); } if (isEntityName(node) || isEntityNameExpression(node)) { const leftmost = getFirstIdentifier(node); if (isInJSFile(node) && (isExportsIdentifier(leftmost) || isModuleExportsAccessExpression(leftmost.parent) || (isQualifiedName(leftmost.parent) && isModuleIdentifier(leftmost.parent.left) && isExportsIdentifier(leftmost.parent.right)))) { hadError = true; return node; } const sym = resolveEntityName(leftmost, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveALias*/ true); if (sym) { if (isSymbolAccessible(sym, context.enclosingDeclaration, SymbolFlags.All, /*shouldComputeAliasesToMakeVisible*/ false).accessibility !== SymbolAccessibility.Accessible) { hadError = true; } else { context.tracker?.trackSymbol?.(sym, context.enclosingDeclaration, SymbolFlags.All); includePrivateSymbol?.(sym); } if (isIdentifier(node)) { const name = sym.flags & SymbolFlags.TypeParameter ? typeParameterToName(getDeclaredTypeOfSymbol(sym), context) : factory.cloneNode(node); name.symbol = sym; // for quickinfo, which uses identifier symbol information return setEmitFlags(setOriginalNode(name, node), EmitFlags.NoAsciiEscaping); } } } if (file && isTupleTypeNode(node) && (getLineAndCharacterOfPosition(file, node.pos).line === getLineAndCharacterOfPosition(file, node.end).line)) { setEmitFlags(node, EmitFlags.SingleLine); } return visitEachChild(node, visitExistingNodeTreeSymbols, nullTransformationContext); function getEffectiveDotDotDotForParameter(p: ParameterDeclaration) { return p.dotDotDotToken || (p.type && isJSDocVariadicType(p.type) ? factory.createToken(SyntaxKind.DotDotDotToken) : undefined); } /** Note that `new:T` parameters are not handled, but should be before calling this function. */ function getNameForJSDocFunctionParameter(p: ParameterDeclaration, index: number) { return p.name && isIdentifier(p.name) && p.name.escapedText === "this" ? "this" : getEffectiveDotDotDotForParameter(p) ? `args` : `arg${index}`; } function rewriteModuleSpecifier(parent: ImportTypeNode, lit: StringLiteral) { if (bundled) { if (context.tracker && context.tracker.moduleResolverHost) { const targetFile = getExternalModuleFileFromDeclaration(parent); if (targetFile) { const getCanonicalFileName = createGetCanonicalFileName(!!host.useCaseSensitiveFileNames); const resolverHost = { getCanonicalFileName, getCurrentDirectory: () => context.tracker.moduleResolverHost!.getCurrentDirectory(), getCommonSourceDirectory: () => context.tracker.moduleResolverHost!.getCommonSourceDirectory() }; const newName = getResolvedExternalModuleName(resolverHost, targetFile); return factory.createStringLiteral(newName); } } } else { if (context.tracker && context.tracker.trackExternalModuleSymbolOfImportTypeNode) { const moduleSym = resolveExternalModuleNameWorker(lit, lit, /*moduleNotFoundError*/ undefined); if (moduleSym) { context.tracker.trackExternalModuleSymbolOfImportTypeNode(moduleSym); } } } return lit; } } } function symbolTableToDeclarationStatements(symbolTable: SymbolTable, context: NodeBuilderContext, bundled?: boolean): Statement[] { const serializePropertySymbolForClass = makeSerializePropertySymbol(factory.createPropertyDeclaration, SyntaxKind.MethodDeclaration, /*useAcessors*/ true); const serializePropertySymbolForInterfaceWorker = makeSerializePropertySymbol((_decorators, mods, name, question, type) => factory.createPropertySignature(mods, name, question, type), SyntaxKind.MethodSignature, /*useAcessors*/ 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: ESMap[] = []; const oldcontext = context; context = { ...oldcontext, usedSymbolNames: new Set(oldcontext.usedSymbolNames), remappedSymbolNames: new Map(), tracker: { ...oldcontext.tracker, trackSymbol: (sym, decl, meaning) => { const accessibleResult = isSymbolAccessible(sym, decl, meaning, /*computeALiases*/ 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)) { includePrivateSymbol(chain[0]); } } else if (oldcontext.tracker && oldcontext.tracker.trackSymbol) { oldcontext.tracker.trackSymbol(sym, decl, meaning); } } } }; forEachEntry(symbolTable, (symbol, name) => { const baseName = unescapeLeadingUnderscores(name); void getInternalSymbolName(symbol, baseName); // Called to cache values into `usedSymbolNames` and `remappedSymbolNames` }); let addingDeclare = !bundled; const exportEquals = symbolTable.get(InternalSymbolName.ExportEquals); if (exportEquals && symbolTable.size > 1 && exportEquals.flags & SymbolFlags.Alias) { 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.decorators, ns.modifiers, ns.name, body = factory.updateModuleBlock( body, factory.createNodeArray([...ns.body.statements, factory.createExportDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports(map(flatMap(excessExports, e => getNamesOfDeclaration(e)), id => factory.createExportSpecifier(/*alias*/ 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( /*decorators*/ undefined, /*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.indexOf(s as ExportDeclaration) === -1), factory.createExportDeclaration( /*decorators*/ undefined, /*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.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) { // export {name} - look thru `statements` for `name`, and if all results can take an `export` modifier, do so and filter it const indices = indicesOf(statements); const associatedIndices = filter(indices, i => nodeHasName(statements[i], e.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.decorators, exportDecl.modifiers, exportDecl.isTypeOnly, factory.updateNamedExports( exportDecl.exportClause, replacements ), exportDecl.moduleSpecifier ); } } 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 canHaveExportModifier(node: Statement): node is Extract { return isEnumDeclaration(node) || isVariableStatement(node) || isFunctionDeclaration(node) || isClassDeclaration(node) || (isModuleDeclaration(node) && !isExternalModuleAugmentation(node) && !isGlobalScopeAugmentation(node)) || isInterfaceDeclaration(node) || isTypeDeclaration(node); } function addExportModifier(node: Extract) { const flags = (getEffectiveModifierFlags(node) | ModifierFlags.Export) & ~ModifierFlags.Ambient; return factory.updateModifiers(node, flags); } function removeExportModifier(node: Extract) { const flags = getEffectiveModifierFlags(node) & ~ModifierFlags.Export; return factory.updateModifiers(node, flags); } function visitSymbolTable(symbolTable: SymbolTable, suppressNewPrivateContext?: boolean, propertyAsAlias?: boolean) { if (!suppressNewPrivateContext) { deferredPrivatesStack.push(new Map()); } symbolTable.forEach((symbol: Symbol) => { serializeSymbol(symbol, /*isPrivate*/ false, !!propertyAsAlias); }); if (!suppressNewPrivateContext) { // deferredPrivates will be filled up by visiting the symbol table // And will continue to iterate as elements are added while visited `deferredPrivates` // (As that's how a map iterator is defined to work) deferredPrivatesStack[deferredPrivatesStack.length - 1].forEach((symbol: Symbol) => { serializeSymbol(symbol, /*isPrivate*/ true, !!propertyAsAlias); }); deferredPrivatesStack.pop(); } } function serializeSymbol(symbol: Symbol, isPrivate: boolean, propertyAsAlias: boolean) { // 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 oldContext = context; context = cloneNodeBuilderContext(context); const result = serializeSymbolWorker(symbol, isPrivate, propertyAsAlias); context = oldContext; return result; } } // 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) { const symbolName = unescapeLeadingUnderscores(symbol.escapedName); const isDefault = symbol.escapedName === 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) && symbol.escapedName !== 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) && symbol.escapedName !== InternalSymbolName.ExportEquals && !(symbol.flags & SymbolFlags.Prototype) && !(symbol.flags & SymbolFlags.Class) && !isConstMergedWithNSPrintableAsSignatureMerge) { if (propertyAsAlias) { const createdExport = serializeMaybeAliasAssignment(symbol); if (createdExport) { needsExportDeclaration = false; needsPostExportDefault = false; } } else { const type = getTypeOfSymbol(symbol); const localName = getInternalSymbolName(symbol, symbolName); 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) ? undefined : isConstVariable(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 = find(symbol.declarations, isPropertyAccessExpression); if (propertyAccessRequire && isBinaryExpression(propertyAccessRequire.parent) && isIdentifier(propertyAccessRequire.parent.right) && type.symbol && isSourceFile(type.symbol.valueDeclaration)) { const alias = localName === propertyAccessRequire.parent.right.escapedText ? undefined : propertyAccessRequire.parent.right; addResult( factory.createExportDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([factory.createExportSpecifier(alias, localName)]) ), ModifierFlags.None ); context.tracker.trackSymbol!(type.symbol, context.enclosingDeclaration, SymbolFlags.Value); } else { const statement = setTextRange(factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, type, symbol, enclosingDeclaration, includePrivateSymbol, bundled)) ], flags)), textRange); 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` addResult( factory.createExportDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([factory.createExportSpecifier(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 && 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 for (const node of symbol.declarations) { const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); if (!resolvedModule) continue; addResult(factory.createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, /*exportClause*/ undefined, factory.createStringLiteral(getSpecifierForModuleSymbol(resolvedModule, context))), ModifierFlags.None); } } if (needsPostExportDefault) { addResult(factory.createExportAssignment(/*decorators*/ undefined, /*modifiers*/ undefined, /*isExportAssignment*/ false, factory.createIdentifier(getInternalSymbolName(symbol, symbolName))), ModifierFlags.None); } else if (needsExportDeclaration) { addResult(factory.createExportDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([factory.createExportSpecifier(getInternalSymbolName(symbol, symbolName), symbolName)]) ), ModifierFlags.None); } } function includePrivateSymbol(symbol: Symbol) { if (some(symbol.declarations, isParameterDeclaration)) 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)) { let newModifierFlags: ModifierFlags = ModifierFlags.None; if (additionalModifierFlags & ModifierFlags.Export && context.enclosingDeclaration && (isExportingScope(context.enclosingDeclaration) || isModuleDeclaration(context.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) && (!context.enclosingDeclaration || !(context.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.updateModifiers(node, newModifierFlags | getEffectiveModifierFlags(node)); } } 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 = find(symbol.declarations, isJSDocTypeAlias); const commentText = jsdocAliasDecl ? jsdocAliasDecl.comment || jsdocAliasDecl.parent.comment : undefined; const oldFlags = context.flags; context.flags |= NodeBuilderFlags.InTypeAlias; addResult(setSyntheticLeadingComments( factory.createTypeAliasDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, getInternalSymbolName(symbol, symbolName), typeParamDecls, typeToTypeNodeHelper(aliasType, context)), !commentText ? [] : [{ kind: SyntaxKind.MultiLineCommentTrivia, text: "*\n * " + commentText.replace(/\n/g, "\n * ") + "\n ", pos: -1, end: -1, hasTrailingNewLine: true }] ), modifierFlags); context.flags = oldFlags; } function serializeInterface(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { 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 = flatMap(getPropertiesOfType(interfaceType), p => serializePropertySymbolForInterface(p, 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( /*decorators*/ undefined, /*modifiers*/ undefined, getInternalSymbolName(symbol, symbolName), typeParamDecls, heritageClauses, [...indexSignatures, ...constructSignatures, ...callSignatures, ...members] ), modifierFlags); } function getNamespaceMembersForSerialization(symbol: Symbol) { return !symbol.exports ? [] : filter(arrayFrom(symbol.exports.values()), isNamespaceMember); } function isTypeOnlyNamespace(symbol: Symbol) { return every(getNamespaceMembersForSerialization(symbol), m => !(resolveSymbol(m).flags & SymbolFlags.Value)); } function serializeModule(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { const members = getNamespaceMembersForSerialization(symbol); // 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 ? "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)) { const localName = getInternalSymbolName(symbol, symbolName); 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( /*decorators*/ undefined, /*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(name === targetName ? undefined : targetName, name); })) )]); addResult(factory.createModuleDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, factory.createIdentifier(localName), nsBody, NodeFlags.Namespace ), ModifierFlags.None); } } function serializeEnum(symbol: Symbol, symbolName: string, modifierFlags: ModifierFlags) { addResult(factory.createEnumDeclaration( /*decorators*/ undefined, factory.createModifiersFromModifierFlags(isConstEnumSymbol(symbol) ? ModifierFlags.Const : 0), getInternalSymbolName(symbol, symbolName), map(filter(getPropertiesOfType(getTypeOfSymbol(symbol)), p => !!(p.flags & SymbolFlags.EnumMember)), p => { // TODO: Handle computed names // I hate that to get the initialized value we need to walk back to the declarations here; but there's no // other way to get the possible const value of an enum member that I'm aware of, as the value is cached // _on the declaration_, not on the declaration's symbol... const initializedValue = p.declarations && p.declarations[0] && isEnumMember(p.declarations[0]) ? getConstantValue(p.declarations[0] as EnumMember) : undefined; return factory.createEnumMember(unescapeLeadingUnderscores(p.escapedName), initializedValue === undefined ? undefined : typeof initializedValue === "string" ? factory.createStringLiteral(initializedValue) : factory.createNumericLiteral(initializedValue)); }) ), modifierFlags); } function serializeAsFunctionNamespaceMerge(type: Type, symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { const signatures = getSignaturesOfType(type, SignatureKind.Call); for (const sig of signatures) { // Each overload becomes a separate function declaration, in order const decl = signatureToSignatureDeclarationHelper(sig, SyntaxKind.FunctionDeclaration, context, { name: factory.createIdentifier(localName), privateSymbolVisitor: includePrivateSymbol, bundledImports: bundled }) as FunctionDeclaration; // for expressions assigned to `var`s, use the `var` as the text range addResult(setTextRange(decl, sig.declaration && isVariableDeclaration(sig.declaration.parent) && sig.declaration.parent.parent || sig.declaration), modifierFlags); } // Module symbol emit will take care of module-y members, provided it has exports if (!(symbol.flags & (SymbolFlags.ValueModule | SymbolFlags.NamespaceModule) && !!symbol.exports && !!symbol.exports.size)) { const props = filter(getPropertiesOfType(type), isNamespaceMember); serializeAsNamespaceDeclaration(props, localName, modifierFlags, /*suppressNewPrivateContext*/ true); } } function serializeAsNamespaceDeclaration(props: readonly Symbol[], localName: string, modifierFlags: ModifierFlags, suppressNewPrivateContext: boolean) { if (length(props)) { const localVsRemoteMap = arrayToMultiMap(props, p => !length(p.declarations) || some(p.declarations, d => getSourceFileOfNode(d) === getSourceFileOfNode(context.enclosingDeclaration!) ) ? "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(/*decorators*/ undefined, /*modifiers*/ undefined, factory.createIdentifier(localName), factory.createModuleBlock([]), NodeFlags.Namespace); 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( /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([factory.createExportSpecifier(d.expression, factory.createIdentifier(InternalSymbolName.Default))]) ) : d); const exportModifierStripped = every(defaultReplaced, d => hasSyntacticModifier(d, ModifierFlags.Export)) ? map(defaultReplaced, removeExportModifier) : defaultReplaced; fakespace = factory.updateModuleDeclaration( fakespace, fakespace.decorators, fakespace.modifiers, fakespace.name, factory.createModuleBlock(exportModifierStripped)); addResult(fakespace, modifierFlags); // namespaces can never be default exported } } function isNamespaceMember(p: Symbol) { return !!(p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias)) || !(p.flags & SymbolFlags.Prototype || p.escapedName === "prototype" || p.valueDeclaration && getEffectiveModifierFlags(p.valueDeclaration) & ModifierFlags.Static && isClassLike(p.valueDeclaration.parent)); } function serializeAsClass(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) { const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol); const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context)); const classType = getDeclaredTypeOfClassOrInterface(symbol); const baseTypes = getBaseTypes(classType); const implementsExpressions = 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; const heritageClauses = [ ...!length(baseTypes) ? [] : [factory.createHeritageClause(SyntaxKind.ExtendsKeyword, map(baseTypes, b => serializeBaseType(b, staticBaseType, localName)))], ...!length(implementsExpressions) ? [] : [factory.createHeritageClause(SyntaxKind.ImplementsKeyword, implementsExpressions)] ]; const symbolProps = getNonInterhitedProperties(classType, baseTypes, getPropertiesOfType(classType)); const publicSymbolProps = filter(symbolProps, s => { // `valueDeclaration` could be undefined if inherited from // a union/intersection base type, but inherited properties // don't matter here. const valueDecl = s.valueDeclaration; return valueDecl && !(isNamedDeclaration(valueDecl) && isPrivateIdentifier(valueDecl.name)); }); const hasPrivateIdentifier = some(symbolProps, s => { // `valueDeclaration` could be undefined if inherited from // a union/intersection base type, but inherited properties // don't matter here. const valueDecl = s.valueDeclaration; return valueDecl && isNamedDeclaration(valueDecl) && isPrivateIdentifier(valueDecl.name); }); // Boil down all private properties into a single one. const privateProperties = hasPrivateIdentifier ? [factory.createPropertyDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, factory.createPrivateIdentifier("#private"), /*questionOrExclamationToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined, )] : emptyArray; const publicProperties = flatMap(publicSymbolProps, p => serializePropertySymbolForClass(p, /*isStatic*/ false, baseTypes[0])); // Consider static members empty if symbol also has function or module meaning - function namespacey emit will handle statics const staticMembers = flatMap( filter(getPropertiesOfType(staticType), p => !(p.flags & SymbolFlags.Prototype) && p.escapedName !== "prototype" && !isNamespaceMember(p)), p => serializePropertySymbolForClass(p, /*isStatic*/ true, staticBaseType)); // 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)); const constructors = isNonConstructableClassLikeInJsFile ? [factory.createConstructorDeclaration(/*decorators*/ undefined, factory.createModifiersFromModifierFlags(ModifierFlags.Private), [], /*body*/ undefined)] : serializeSignatures(SignatureKind.Construct, staticType, baseTypes[0], SyntaxKind.Constructor) as ConstructorDeclaration[]; const indexSignatures = serializeIndexSignatures(classType, baseTypes[0]); addResult(setTextRange(factory.createClassDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, localName, typeParamDecls, heritageClauses, [...indexSignatures, ...staticMembers, ...constructors, ...publicProperties, ...privateProperties] ), symbol.declarations && filter(symbol.declarations, d => isClassDeclaration(d) || isClassExpression(d))[0]), modifierFlags); } 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; } let verbatimTargetName = unescapeLeadingUnderscores(target.escapedName); if (verbatimTargetName === InternalSymbolName.ExportEquals && (compilerOptions.esModuleInterop || compilerOptions.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; addResult(factory.createImportDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamedImports([factory.createImportSpecifier( propertyName && isIdentifier(propertyName) ? factory.createIdentifier(idText(propertyName)) : undefined, factory.createIdentifier(localName) )])), factory.createStringLiteral(specifier) ), 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'); addResult(factory.createImportEqualsDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, uniqueName, factory.createExternalModuleReference(factory.createStringLiteral(specifier)) ), ModifierFlags.None); // import x = _x.z addResult(factory.createImportEqualsDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, 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, isJsonSourceFile)) { 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); addResult(factory.createImportEqualsDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, 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: addResult(factory.createImportDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, factory.createImportClause(/*isTypeOnly*/ false, factory.createIdentifier(localName), /*namedBindings*/ undefined), // We use `target.parent || target` below as `target.parent` is unset when the target is a module which has been export assigned // And then made into a default by the `esModuleInterop` or `allowSyntheticDefaultImports` flag // In such cases, the `target` refers to the module itself already factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context)) ), ModifierFlags.None); break; case SyntaxKind.NamespaceImport: addResult(factory.createImportDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, factory.createImportClause(/*isTypeOnly*/ false, /*importClause*/ undefined, factory.createNamespaceImport(factory.createIdentifier(localName))), factory.createStringLiteral(getSpecifierForModuleSymbol(target, context)) ), ModifierFlags.None); break; case SyntaxKind.NamespaceExport: addResult(factory.createExportDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamespaceExport(factory.createIdentifier(localName)), factory.createStringLiteral(getSpecifierForModuleSymbol(target, context)) ), ModifierFlags.None); break; case SyntaxKind.ImportSpecifier: addResult(factory.createImportDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, factory.createImportClause( /*isTypeOnly*/ false, /*importClause*/ undefined, factory.createNamedImports([ factory.createImportSpecifier( localName !== verbatimTargetName ? factory.createIdentifier(verbatimTargetName) : undefined, factory.createIdentifier(localName) ) ])), factory.createStringLiteral(getSpecifierForModuleSymbol(target.parent || target, context)) ), 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; // 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) { addResult(factory.createExportDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, /*isTypeOnly*/ false, factory.createNamedExports([factory.createExportSpecifier(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 oldTrack = context.tracker.trackSymbol; context.tracker.trackSymbol = noop; if (isExportAssignmentCompatibleSymbolName) { results.push(factory.createExportAssignment( /*decorators*/ undefined, /*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); addResult(factory.createImportEqualsDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, factory.createIdentifier(varName), symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false) ), ModifierFlags.None); serializeExportSpecifier(name, varName); } } context.tracker.trackSymbol = oldTrack; 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 statement = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ factory.createVariableDeclaration(varName, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, typeToSerialize, symbol, enclosingDeclaration, includePrivateSymbol, bundled)) ], NodeFlags.Const)); // 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) { results.push(factory.createExportAssignment( /*decorators*/ undefined, /*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) && !getIndexInfoOfType(typeToSerialize, IndexKind.String) && !getIndexInfoOfType(typeToSerialize, IndexKind.Number) && !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 => isIdentifierText(symbolName(p), languageVersion)); } function makeSerializePropertySymbol(createProperty: ( decorators: readonly Decorator[] | undefined, 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: ( decorators: readonly Decorator[] | undefined, 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: ( decorators: readonly Decorator[] | undefined, 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 isPrivate = !!(modifierFlags & ModifierFlags.Private); 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 || (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 = find(p.declarations, or(isPropertyDeclaration, isAccessor, isVariableDeclaration, isPropertySignature, isBinaryExpression, isPropertyAccessExpression)); if (p.flags & SymbolFlags.Accessor && useAccessors) { const result: AccessorDeclaration[] = []; if (p.flags & SymbolFlags.SetAccessor) { result.push(setTextRange(factory.createSetAccessorDeclaration( /*decorators*/ undefined, factory.createModifiersFromModifierFlags(flag), name, [factory.createParameterDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, "arg", /*questionToken*/ undefined, isPrivate ? undefined : serializeTypeForDeclaration(context, getTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled) )], /*body*/ undefined ), find(p.declarations, isSetAccessor) || firstPropertyLikeDecl)); } if (p.flags & SymbolFlags.GetAccessor) { const isPrivate = modifierFlags & ModifierFlags.Private; result.push(setTextRange(factory.createGetAccessorDeclaration( /*decorators*/ undefined, factory.createModifiersFromModifierFlags(flag), name, [], isPrivate ? undefined : serializeTypeForDeclaration(context, getTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled), /*body*/ undefined ), find(p.declarations, isGetAccessor) || 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)) { return setTextRange(createProperty( /*decorators*/ undefined, factory.createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | flag), name, p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, isPrivate ? undefined : serializeTypeForDeclaration(context, getTypeOfSymbol(p), p, enclosingDeclaration, includePrivateSymbol, bundled), // TODO: https://github.com/microsoft/TypeScript/pull/32372#discussion_r328386357 // interface members can't have initializers, however class members _can_ /*initializer*/ undefined ), find(p.declarations, or(isPropertyDeclaration, isVariableDeclaration)) || firstPropertyLikeDecl); } if (p.flags & (SymbolFlags.Method | SymbolFlags.Function)) { const type = getTypeOfSymbol(p); const signatures = getSignaturesOfType(type, SignatureKind.Call); if (flag & ModifierFlags.Private) { return setTextRange(createProperty( /*decorators*/ undefined, factory.createModifiersFromModifierFlags((isReadonlySymbol(p) ? ModifierFlags.Readonly : 0) | flag), name, p.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined, /*type*/ undefined, /*initializer*/ undefined ), find(p.declarations, isFunctionLikeDeclaration) || signatures[0] && signatures[0].declaration || p.declarations[0]); } const results = []; for (const sig of signatures) { // 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 } ); results.push(setTextRange(decl, sig.declaration)); } 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 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 < baseSigs.length; i++) { if (!compareSignaturesIdentical(signatures[i], baseSigs[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { failed = true; break; } } if (!failed) { return []; // Every signature was identical - elide constructor list as it is inherited } } } let privateProtected: ModifierFlags = 0; for (const s of signatures) { if (s.declaration) { privateProtected |= getSelectedEffectiveModifierFlags(s.declaration, ModifierFlags.Private | ModifierFlags.Protected); } } if (privateProtected) { return [setTextRange(factory.createConstructorDeclaration( /*decorators*/ undefined, factory.createModifiersFromModifierFlags(privateProtected), /*parameters*/ [], /*body*/ undefined, ), signatures[0].declaration)]; } } const results = []; for (const sig of signatures) { // Each overload becomes a separate constructor declaration, in order const decl = signatureToSignatureDeclarationHelper(sig, outputKind, context); results.push(setTextRange(decl, sig.declaration)); } return results; } function serializeIndexSignatures(input: Type, baseType: Type | undefined) { const results: IndexSignatureDeclaration[] = []; for (const type of [IndexKind.String, IndexKind.Number]) { const info = getIndexInfoOfType(input, type); if (info) { if (baseType) { const baseInfo = getIndexInfoOfType(baseType, type); if (baseInfo) { if (isTypeIdenticalTo(info.type, baseInfo.type)) { continue; // elide identical index signatures } } } results.push(indexInfoToIndexSignatureDeclarationHelper(info, type, context, /*typeNode*/ undefined)); } } return results; } function serializeBaseType(t: Type, staticType: Type, rootName: string) { const ref = trySerializeAsTypeReference(t, SymbolFlags.Value); if (ref) { return ref; } const tempName = getUnusedName(`${rootName}_base`); const statement = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([ factory.createVariableDeclaration(tempName, /*exclamationToken*/ undefined, typeToTypeNodeHelper(staticType, context)) ], NodeFlags.Const)); addResult(statement, ModifierFlags.None); return factory.createExpressionWithTypeArguments(factory.createIdentifier(tempName), /*typeArgs*/ undefined); } function trySerializeAsTypeReference(t: Type, flags: SymbolFlags) { let typeArgs: TypeNode[] | undefined; let reference: Expression | undefined; // We don't use `isValueSymbolAccessible` below. since that considers alternative containers (like modules) // which we can't write out in a syntactically valid way as an expression if ((t as TypeReference).target && isSymbolAccessibleByFlags((t as TypeReference).target.symbol, enclosingDeclaration, flags)) { typeArgs = map(getTypeArguments(t as TypeReference), t => 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), /*typeArgs*/ 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 flags = context.flags; context.flags |= NodeBuilderFlags.InInitialEntityName; const nameCandidate = getNameOfSymbolAsWritten(symbol, context); context.flags = flags; 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-zA-Z0-9]/g, "_"); 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 typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Node, flags: TypeFormatFlags = TypeFormatFlags.UseAliasDefinedOutsideCurrentScope, writer?: EmitTextWriter): string { return writer ? typePredicateToStringWorker(writer).getText() : usingSingleLineStringWriter(typePredicateToStringWorker); function typePredicateToStringWorker(writer: EmitTextWriter) { const predicate = factory.createTypePredicateNode( typePredicate.kind === TypePredicateKind.AssertsThis || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? factory.createToken(SyntaxKind.AssertsKeyword) : undefined, typePredicate.kind === TypePredicateKind.Identifier || typePredicate.kind === TypePredicateKind.AssertsIdentifier ? factory.createIdentifier(typePredicate.parameterName) : factory.createThisTypeNode(), typePredicate.type && nodeBuilder.typeToTypeNode(typePredicate.type, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName)! // TODO: GH#18217 ); const printer = createPrinter({ removeComments: true }); const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); printer.writeNode(EmitHint.Unspecified, predicate, /*sourceFile*/ sourceFile, writer); return writer; } } function formatUnionTypes(types: readonly Type[]): Type[] { const result: Type[] = []; let flags: TypeFlags = 0; for (let i = 0; i < types.length; i++) { const t = types[i]; flags |= t.flags; if (!(t.flags & TypeFlags.Nullable)) { if (t.flags & (TypeFlags.BooleanLiteral | TypeFlags.EnumLiteral)) { const baseType = t.flags & TypeFlags.BooleanLiteral ? booleanType : getBaseTypeOfEnumLiteralType(t); if (baseType.flags & TypeFlags.Union) { const count = (baseType).types.length; if (i + count <= types.length && getRegularTypeOfLiteralType(types[i + count - 1]) === getRegularTypeOfLiteralType((baseType).types[count - 1])) { result.push(baseType); i += count - 1; continue; } } } result.push(t); } } if (flags & TypeFlags.Null) result.push(nullType); if (flags & TypeFlags.Undefined) result.push(undefinedType); return result || types; } function visibilityToString(flags: ModifierFlags): string | undefined { if (flags === ModifierFlags.Private) { return "private"; } if (flags === ModifierFlags.Protected) { return "protected"; } return "public"; } function getTypeAliasForTypeLiteral(type: Type): Symbol | undefined { if (type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral) { const node = walkUpParenthesizedTypes(type.symbol.declarations[0].parent); if (node.kind === SyntaxKind.TypeAliasDeclaration) { return getSymbolOfNode(node); } } return undefined; } function isTopLevelInExternalModuleAugmentation(node: Node): boolean { return node && node.parent && node.parent.kind === SyntaxKind.ModuleBlock && isExternalModuleAugmentation(node.parent.parent); } interface NodeBuilderContext { enclosingDeclaration: Node | undefined; flags: NodeBuilderFlags; tracker: SymbolTracker; // State encounteredError: boolean; visitedTypes: Set | undefined; symbolDepth: ESMap | undefined; inferTypeParameters: TypeParameter[] | undefined; approximateLength: number; truncating?: boolean; typeParameterSymbolList?: Set; typeParameterNames?: ESMap; typeParameterNamesByText?: Set; usedSymbolNames?: Set; remappedSymbolNames?: ESMap; } function isDefaultBindingContext(location: Node) { return location.kind === SyntaxKind.SourceFile || isAmbientModule(location); } function getNameOfSymbolFromNameType(symbol: Symbol, context?: NodeBuilderContext) { const nameType = getSymbolLinks(symbol).nameType; if (nameType) { if (nameType.flags & TypeFlags.StringOrNumberLiteral) { const name = "" + (nameType).value; if (!isIdentifierText(name, compilerOptions.target) && !isNumericLiteralName(name)) { return `"${escapeString(name, CharacterCodes.doubleQuote)}"`; } if (isNumericLiteralName(name) && startsWith(name, "-")) { return `[${name}]`; } return name; } if (nameType.flags & TypeFlags.UniqueESSymbol) { return `[${getNameOfSymbolAsWritten((nameType).symbol, context)}]`; } } } /** * Gets a human-readable name for a symbol. * Should *not* be used for the right-hand side of a `.` -- use `symbolName(symbol)` for that instead. * * Unlike `symbolName(symbol)`, this will include quotes if the name is from a string literal. * It will also use a representation of a number as written instead of a decimal form, e.g. `0o11` instead of `9`. */ function getNameOfSymbolAsWritten(symbol: Symbol, context?: NodeBuilderContext): string { if (context && symbol.escapedName === InternalSymbolName.Default && !(context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope) && // If it's not the first part of an entity name, it must print as `default` (!(context.flags & NodeBuilderFlags.InInitialEntityName) || // if the symbol is synthesized, it will only be referenced externally it must print as `default` !symbol.declarations || // if not in the same binding context (source file, module declaration), it must print as `default` (context.enclosingDeclaration && findAncestor(symbol.declarations[0], isDefaultBindingContext) !== findAncestor(context.enclosingDeclaration, isDefaultBindingContext)))) { return "default"; } if (symbol.declarations && symbol.declarations.length) { let declaration = firstDefined(symbol.declarations, d => 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).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 (!(getCombinedModifierFlags(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: Identifier, setVisibility?: boolean): Node[] | undefined { let exportSymbol: Symbol | undefined; if (node.parent && node.parent.kind === SyntaxKind.ExportAssignment) { exportSymbol = resolveName(node, node.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, node, /*isUse*/ false); } else if (node.parent.kind === SyntaxKind.ExportSpecifier) { exportSymbol = getTargetOfExportSpecifier(node.parent, 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[]) { 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; const firstIdentifier = getFirstIdentifier(internalModuleReference); const importSymbol = resolveName(declaration, firstIdentifier.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, undefined, 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 < length; i++) { resolutionResults[i] = false; } return false; } resolutionTargets.push(target); resolutionResults.push(/*items*/ true); resolutionPropertyNames.push(propertyName); return true; } function findResolutionCycleStartIndex(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): number { for (let i = resolutionTargets.length - 1; i >= 0; i--) { if (hasType(resolutionTargets[i], resolutionPropertyNames[i])) { return -1; } if (resolutionTargets[i] === target && resolutionPropertyNames[i] === propertyName) { return i; } } return -1; } function hasType(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { switch (propertyName) { case TypeSystemPropertyName.Type: return !!getSymbolLinks(target).type; case TypeSystemPropertyName.EnumTagType: return !!(getNodeLinks(target as JSDocEnumTag).resolvedEnumType); case TypeSystemPropertyName.DeclaredType: return !!getSymbolLinks(target).declaredType; case TypeSystemPropertyName.ResolvedBaseConstructorType: return !!(target).resolvedBaseConstructorType; case TypeSystemPropertyName.ResolvedReturnType: return !!(target).resolvedReturnType; case TypeSystemPropertyName.ImmediateBaseConstraint: return !!(target).immediateBaseConstraint; case TypeSystemPropertyName.ResolvedTypeArguments: return !!(target as TypeReference).resolvedTypeArguments; case TypeSystemPropertyName.ResolvedBaseTypes: return !!(target as InterfaceType).baseTypesResolved; } 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)!); return classType.typeParameters ? createTypeReference(classType, 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; } function getTypeOfPropertyOrIndexSignature(type: Type, name: __String): Type { return getTypeOfPropertyOfType(type, name) || isNumericLiteralName(name) && getIndexTypeOfType(type, IndexKind.Number) || getIndexTypeOfType(type, IndexKind.String) || unknownType; } function isTypeAny(type: Type | undefined) { return type && (type.flags & TypeFlags.Any) !== 0; } // 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) { const symbol = getSymbolOfNode(node); return symbol && getSymbolLinks(symbol).type || getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ false); } 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)); } const omitKeyType = getUnionType(map(properties, getLiteralTypeFromPropertyName)); if (isGenericObjectType(source) || isGenericIndexType(omitKeyType)) { 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 getPropertiesOfType(source)) { if (!isTypeAssignableTo(getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique), omitKeyType) && !(getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) && isSpreadableProperty(prop)) { members.set(prop.escapedName, getSpreadSymbol(prop, /*readonly*/ false)); } } const stringIndexInfo = getIndexInfoOfType(source, IndexKind.String); const numberIndexInfo = getIndexInfoOfType(source, IndexKind.Number); const result = createAnonymousType(symbol, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); result.objectFlags |= ObjectFlags.ObjectRestType; return result; } // 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 && parentAccess.flowNode) { const propName = getDestructuringPropertyName(node); if (propName) { const literal = setTextRange(parseNodeFactory.createStringLiteral(propName), node); const lhsExpr = isLeftHandSideExpression(parentAccess) ? parentAccess : parseNodeFactory.createParenthesizedExpression(parentAccess); const result = setTextRange(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); case SyntaxKind.ArrayLiteralExpression: return getSyntheticElementAccess(node.parent); case SyntaxKind.VariableDeclaration: return (ancestor).initializer; case SyntaxKind.BinaryExpression: return (ancestor).right; } } function getDestructuringPropertyName(node: BindingElement | PropertyAssignment | ShorthandPropertyAssignment | Expression) { const parent = node.parent; if (node.kind === SyntaxKind.BindingElement && parent.kind === SyntaxKind.ObjectBindingPattern) { return getLiteralPropertyNameText((node).propertyName || (node).name); } if (node.kind === SyntaxKind.PropertyAssignment || node.kind === SyntaxKind.ShorthandPropertyAssignment) { return getLiteralPropertyNameText((node).name); } return "" + (>(parent).elements).indexOf(node); } function getLiteralPropertyNameText(name: PropertyName) { const type = getLiteralTypeFromPropertyName(name); return type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral) ? "" + (type).value : undefined; } /** Return the inferred type for a binding element */ function getTypeForBindingElement(declaration: BindingElement): Type | undefined { const pattern = declaration.parent; let parentType = getTypeForBindingElementParent(pattern.parent); // If no type or an any type was inferred for parent, infer that for the binding element if (!parentType || isTypeAny(parentType)) { return parentType; } // Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(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 && !(getTypeFacts(getTypeOfInitializer(pattern.parent.initializer)) & TypeFacts.EQUndefined)) { parentType = getTypeWithFacts(parentType, TypeFacts.NEUndefined); } 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; const indexType = getLiteralTypeFromPropertyName(name); const declaredType = getConstraintForLocation(getIndexedAccessType(parentType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, name, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, AccessFlags.ExpressionPosition), declaration.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. type = everyType(parentType, isTupleType) ? mapType(parentType, t => sliceTupleType(t, index)) : createArrayType(elementType); } else if (isArrayLikeType(parentType)) { const indexType = getLiteralType(index); const accessFlags = hasDefaultValue(declaration) ? AccessFlags.NoTupleBoundsCheck : 0; const declaredType = getConstraintForLocation(getIndexedAccessTypeOrUndefined(parentType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, declaration.name, accessFlags | AccessFlags.ExpressionPosition) || errorType, declaration.name); 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 && !(getFalsyFlags(checkDeclarationInitializer(declaration)) & TypeFlags.Undefined) ? getTypeWithFacts(type, TypeFacts.NEUndefined) : type; } return widenTypeInferredFromInitializer(declaration, getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), checkDeclarationInitializer(declaration)], 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); return expr.kind === SyntaxKind.NullKeyword || expr.kind === SyntaxKind.Identifier && getResolvedSymbol(expr) === undefinedSymbol; } function isEmptyArrayLiteral(node: Expression) { const expr = skipParentheses(node); return expr.kind === SyntaxKind.ArrayLiteralExpression && (expr).elements.length === 0; } function addOptionality(type: Type, optional = true): Type { return strictNullChecks && optional ? getOptionalType(type) : type; } // Return the inferred type for a variable, parameter, or property declaration function getTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement | JSDocPropertyLikeTag, includeOptionality: boolean): 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))); 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); } const isOptional = includeOptionality && ( isParameter(declaration) && isJSDocOptionalParameter(declaration) || isOptionalJSDocPropertyLikeTag(declaration) || !isBindingElement(declaration) && !isVariableDeclaration(declaration) && !!declaration.questionToken); // Use type from type annotation if one is present const declaredType = tryGetTypeFromEffectiveTypeNode(declaration); if (declaredType) { return addOptionality(declaredType, isOptional); } if ((noImplicitAny || isInJSFile(declaration)) && isVariableDeclaration(declaration) && !isBindingPattern(declaration.name) && !(getCombinedModifierFlags(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 (!(getCombinedNodeFlags(declaration) & NodeFlags.Const) && (!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)) { const func = declaration.parent; // For a parameter of a set accessor, use the type of the get accessor if one is present if (func.kind === SyntaxKind.SetAccessor && !hasNonBindableDynamicName(func)) { const getter = getDeclarationOfKind(getSymbolOfNode(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); } } if (isInJSFile(declaration)) { const typeTag = getJSDocType(func); if (typeTag && isFunctionTypeNode(typeTag)) { const signature = getSignatureFromDeclaration(typeTag); const pos = func.parameters.indexOf(declaration); return declaration.dotDotDotToken ? getRestTypeAtPosition(signature, pos) : getTypeAtPosition(signature, pos); } } // Use contextual parameter type if one is available const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration); if (type) { return addOptionality(type, 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, getSymbolOfNode(declaration), getDeclaredExpandoInitializer(declaration)); if (containerObjectType) { return containerObjectType; } } const type = widenTypeInferredFromInitializer(declaration, checkDeclarationInitializer(declaration)); return addOptionality(type, isOptional); } if (isPropertyDeclaration(declaration) && !hasStaticModifier(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 to determine the type of the property. const constructor = findConstructorDeclaration(declaration.parent); const type = constructor ? getFlowTypeInConstructor(declaration.symbol, constructor) : getEffectiveModifierFlags(declaration) & ModifierFlags.Ambient ? getTypeOfPropertyInBaseClass(declaration.symbol) : undefined; return type && addOptionality(type, 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).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) { for (const declaration of symbol.declarations) { const container = getThisContainer(declaration, /*includeArrowFunctions*/ false); if (container && (container.kind === SyntaxKind.Constructor || isJSConstructor(container))) { return container; } } } 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 && (!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 = getJSDocTypeTag(container); if (tag && tag.typeExpression) { return getTypeFromTypeNode(tag.typeExpression); } const containerObjectType = 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 jsdocType: Type | undefined; let types: 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 ? 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!, UnionReduction.Subtype); } } const widened = getWidenedType(addOptionality(type, definedInMethod && !definedInConstructor)); if (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, undefined, undefined); 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 (declaredType !== errorType && type !== errorType && !isTypeIdenticalTo(declaredType, type)) { errorNextVariableOrPropertyDeclarationMustHaveSameType(/*firstDeclaration*/ undefined, declaredType, declaration, type); } } if (symbol.parent) { const typeNode = getEffectiveTypeAnnotationNode(symbol.parent.valueDeclaration); if (typeNode) { return getTypeOfPropertyOfType(getTypeFromTypeNode(typeNode), symbol.escapedName); } } 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 type = resolvedSymbol ? getTypeOfSymbol(resolvedSymbol) : 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) { 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 (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.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.stringIndexInfo, exportedType.numberIndexInfo); result.objectFlags |= (getObjectFlags(type) & ObjectFlags.JSLiteral); // Propagate JSLiteral flag 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); // 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(widenTypeInferredFromInitializer(element, checkDeclarationInitializer(element, 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 the 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; if (e.dotDotDotToken) { stringIndexInfo = createIndexInfo(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.type = getTypeFromBindingElement(e, includePatternInType, reportErrors); symbol.bindingElement = e; members.set(symbol.escapedName, symbol); }); const result = createAnonymousType(undefined, members, emptyArray, emptyArray, stringIndexInfo, undefined); 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); 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 { return pattern.kind === SyntaxKind.ObjectBindingPattern ? getTypeFromObjectBindingPattern(pattern, includePatternInType, reportErrors) : getTypeFromArrayBindingPattern(pattern, includePatternInType, reportErrors); } // 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), declaration, reportErrors); } function widenTypeForVariableLikeDeclaration(type: Type | undefined, declaration: any, reportErrors?: boolean) { if (type) { 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) || !declaration.type) && type.symbol !== getSymbolOfNode(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(declaration: Declaration) { const typeNode = getEffectiveTypeAnnotationNode(declaration); if (typeNode) { return getTypeFromTypeNode(typeNode); } } 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. if (!links.type) { links.type = type; } } return links.type; } function getTypeOfVariableOrParameterOrPropertyWorker(symbol: Symbol) { // 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) { const fileSymbol = getSymbolOfNode(getSourceFileOfNode(symbol.valueDeclaration)); const members = createSymbolTable(); members.set("exports" as __String, fileSymbol); return createAnonymousType(symbol, members, emptyArray, emptyArray, undefined, undefined); } // Handle catch clause variables const declaration = symbol.valueDeclaration; if (isCatchClauseVariableDeclarationOrBindingElement(declaration)) { const decl = declaration as VariableDeclaration; if (!decl.type) return anyType; const type = getTypeOfNode(decl.type); // an errorType will make `checkTryStatement` issue an error return isTypeAny(type) || type === unknownType ? type : errorType; } // Handle export default expressions if (isSourceFile(declaration) && isJsonSourceFile(declaration)) { if (!declaration.statements.length) { return emptyObjectType; } return getWidenedType(getWidenedLiteralType(checkExpression(declaration.statements[0].expression))); } // 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 | undefined; if (declaration.kind === SyntaxKind.ExportAssignment) { type = widenTypeForVariableLikeDeclaration(checkExpressionCached((declaration).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, /*includeOptionality*/ 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 if (isAccessor(declaration)) { type = resolveTypeOfAccessors(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 | undefined): TypeNode | undefined { if (accessor) { if (accessor.kind === SyntaxKind.GetAccessor) { const getterTypeAnnotation = getEffectiveReturnTypeNode(accessor); return getterTypeAnnotation; } else { const setterTypeAnnotation = getEffectiveSetAccessorTypeAnnotationNode(accessor); return setterTypeAnnotation; } } return undefined; } function getAnnotatedAccessorType(accessor: AccessorDeclaration | 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); return links.type || (links.type = getTypeOfAccessorsWorker(symbol)); } function getTypeOfAccessorsWorker(symbol: Symbol): Type { if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { return errorType; } let type = resolveTypeOfAccessors(symbol); if (!popTypeResolution()) { type = anyType; if (noImplicitAny) { const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); 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)); } } return type; } function resolveTypeOfAccessors(symbol: Symbol) { const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); if (getter && isInJSFile(getter)) { const jsDocType = getTypeForDeclarationFromJSDocComment(getter); if (jsDocType) { return jsDocType; } } // First try to see if the user specified a return type on the get-accessor. const getterReturnType = getAnnotatedAccessorType(getter); if (getterReturnType) { return getterReturnType; } else { // If the user didn't specify a return type, try to use the set-accessor's parameter type. const setterParameterType = getAnnotatedAccessorType(setter); if (setterParameterType) { return setterParameterType; } else { // If there are no specified types, try to infer it from the body of the get accessor if it exists. if (getter && getter.body) { return getReturnTypeFromBody(getter); } // Otherwise, fall back to 'any'. else { if (setter) { if (!isPrivateWithinAmbient(setter)) { errorOrSuggestion(noImplicitAny, setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol)); } } else { Debug.assert(!!getter, "there must exist a getter as we are current checking either setter or getter in this function"); if (!isPrivateWithinAmbient(getter)) { errorOrSuggestion(noImplicitAny, getter, Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation, symbolToString(symbol)); } } return anyType; } } } } 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 = links = merged; } } 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) : 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) { const targetSymbol = resolveAlias(symbol); // 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 = targetSymbol.flags & SymbolFlags.Value ? getTypeOfSymbol(targetSymbol) : errorType; } return links.type; } function getTypeOfInstantiatedSymbol(symbol: Symbol): Type { const links = getSymbolLinks(symbol); if (!links.type) { if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { return links.type = errorType; } let type = instantiateType(getTypeOfSymbol(links.target!), links.mapper); if (!popTypeResolution()) { type = reportCircularityError(symbol); } links.type = type; } return links.type; } function reportCircularityError(symbol: Symbol) { const declaration = symbol.valueDeclaration; // Check if variable has type annotation that circularly references the variable itself 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).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)); } // 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 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 isReferenceToType(type: Type, target: Type) { return type !== undefined && target !== undefined && (getObjectFlags(type) & ObjectFlags.Reference) !== 0 && (type).target === target; } function getTargetType(type: Type): Type { return getObjectFlags(type) & ObjectFlags.Reference ? (type).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); return target === checkBase || some(getBaseTypes(target), check); } else if (type.flags & TypeFlags.Intersection) { return some((type).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(getSymbolOfNode(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 = getSymbolOfNode(node.left); if (symbol && symbol.parent && !findAncestor(symbol.parent.valueDeclaration, d => node === d)) { node = symbol.parent.valueDeclaration; } } } if (!node) { return undefined; } switch (node.kind) { case SyntaxKind.VariableStatement: 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 (node.kind === SyntaxKind.MappedType) { return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode((node).typeParameter))); } else if (node.kind === SyntaxKind.ConditionalType) { return concatenate(outerTypeParameters, getInferTypeParameters(node)); } else if (node.kind === SyntaxKind.VariableStatement && !isInJSFile(node)) { break; } const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(node)); const thisType = includeThisTypes && (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.InterfaceDeclaration || isJSConstructor(node)) && getDeclaredTypeOfClassOrInterface(getSymbolOfNode(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; } } } // 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.valueDeclaration : getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration)!; 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 { 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; 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]; return !s.typeParameters && s.parameters.length === 1 && signatureHasRestParameter(s) && getElementTypeOfArrayType(getTypeOfParameter(s.parameters[0])) === 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 { return getEffectiveBaseTypeNode(type.symbol.valueDeclaration as ClassLikeDeclaration); } 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 <= length(sig.typeParameters)); } function getInstantiatedConstructorsForTypeArguments(type: Type, typeArgumentNodes: readonly TypeNode[] | undefined, location: Node): readonly Signature[] { const signatures = getConstructorsForTypeArguments(type, typeArgumentNodes, location); const typeArguments = map(typeArgumentNodes, getTypeFromTypeNode); return sameMap(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, * * unknownType 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 = type.symbol.valueDeclaration; const extended = 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); } 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]); } } 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; 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 (implementsType !== errorType) { if (resolvedImplementsTypes === emptyArray) { resolvedImplementsTypes = [implementsType]; } 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)]; } 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()) { 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 (baseType === errorType) { 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(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).outerTypeParameters; if (outerTypeParameters) { const last = outerTypeParameters.length - 1; const typeArguments = getTypeArguments(type); 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).types, isValidBaseType)); } function resolveBaseTypesOfInterface(type: InterfaceType): void { type.resolvedBaseTypes = type.resolvedBaseTypes || emptyArray; for (const declaration of type.symbol.declarations) { if (declaration.kind === SyntaxKind.InterfaceDeclaration && getInterfaceBaseTypeNodes(declaration)) { for (const node of getInterfaceBaseTypeNodes(declaration)!) { const baseType = getReducedType(getTypeFromTypeNode(node)); if (baseType !== errorType) { if (isValidBaseType(baseType)) { if (type !== baseType && !hasBaseType(baseType, type)) { if (type.resolvedBaseTypes === emptyArray) { type.resolvedBaseTypes = [baseType]; } 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 { for (const declaration of symbol.declarations) { if (declaration.kind === SyntaxKind.InterfaceDeclaration) { if (declaration.flags & NodeFlags.ContainsThis) { return false; } const baseTypeNodes = getInterfaceBaseTypeNodes(declaration); 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, getAssignedClassSymbol(symbol.valueDeclaration)); if (merged) { // note:we overwrite links because we just cloned the symbol symbol = links = merged; } const type = originalLinks.declaredType = links.declaredType = createObjectType(kind, symbol); 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).instantiations = new Map(); (type).instantiations.set(getTypeListId(type.typeParameters), type); (type).target = type; (type).resolvedTypeArguments = type.typeParameters; type.thisType = createTypeParameter(symbol); type.thisType.isThisType = true; type.thisType.constraint = type; } } return links.declaredType; } 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(find(symbol.declarations, 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); } } else { type = errorType; error(isNamedDeclaration(declaration) ? declaration.name : declaration || declaration, Diagnostics.Type_alias_0_circularly_references_itself, symbolToString(symbol)); } links.declaredType = type; } return links.declaredType; } function isStringConcatExpression(expr: Node): boolean { if (isStringLiteralLike(expr)) { return true; } else if (expr.kind === SyntaxKind.BinaryExpression) { return isStringConcatExpression((expr).left) && isStringConcatExpression((expr).right); } return false; } function isLiteralEnumMember(member: EnumMember) { const expr = member.initializer; if (!expr) { return !(member.flags & NodeFlags.Ambient); } switch (expr.kind) { case SyntaxKind.StringLiteral: case SyntaxKind.NumericLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: return true; case SyntaxKind.PrefixUnaryExpression: return (expr).operator === SyntaxKind.MinusToken && (expr).operand.kind === SyntaxKind.NumericLiteral; case SyntaxKind.Identifier: return nodeIsMissing(expr) || !!getSymbolOfNode(member.parent).exports!.get((expr).escapedText); case SyntaxKind.BinaryExpression: return isStringConcatExpression(expr); default: return false; } } function getEnumKind(symbol: Symbol): EnumKind { const links = getSymbolLinks(symbol); if (links.enumKind !== undefined) { return links.enumKind; } let hasNonLiteralMember = false; for (const declaration of symbol.declarations) { if (declaration.kind === SyntaxKind.EnumDeclaration) { for (const member of (declaration).members) { if (member.initializer && isStringLiteralLike(member.initializer)) { return links.enumKind = EnumKind.Literal; } if (!isLiteralEnumMember(member)) { hasNonLiteralMember = true; } } } } return links.enumKind = hasNonLiteralMember ? EnumKind.Numeric : EnumKind.Literal; } function getBaseTypeOfEnumLiteralType(type: Type) { return type.flags & TypeFlags.EnumLiteral && !(type.flags & TypeFlags.Union) ? getDeclaredTypeOfSymbol(getParentOfSymbol(type.symbol)!) : type; } function getDeclaredTypeOfEnum(symbol: Symbol): Type { const links = getSymbolLinks(symbol); if (links.declaredType) { return links.declaredType; } if (getEnumKind(symbol) === EnumKind.Literal) { enumCount++; const memberTypeList: Type[] = []; for (const declaration of symbol.declarations) { if (declaration.kind === SyntaxKind.EnumDeclaration) { for (const member of (declaration).members) { const value = getEnumMemberValue(member); const memberType = getFreshTypeOfLiteralType(getLiteralType(value !== undefined ? value : 0, enumCount, getSymbolOfNode(member))); getSymbolLinks(getSymbolOfNode(member)).declaredType = memberType; memberTypeList.push(getRegularTypeOfLiteralType(memberType)); } } } if (memberTypeList.length) { const enumType = getUnionType(memberTypeList, UnionReduction.Literal, symbol, /*aliasTypeArguments*/ undefined); if (enumType.flags & TypeFlags.Union) { enumType.flags |= TypeFlags.EnumLiteral; enumType.symbol = symbol; } return links.declaredType = enumType; } } const enumType = createType(TypeFlags.Enum); enumType.symbol = symbol; return links.declaredType = enumType; } 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).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); case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.Constructor: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: return isThislessFunctionLikeDeclaration(declaration); } } } 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 s of baseSymbols) { if (!symbols.has(s.escapedName) && !isStaticPrivateIdentifierProperty(s)) { symbols.set(s.escapedName, s); } } } function isStaticPrivateIdentifierProperty(s: Symbol): boolean { return !!s.valueDeclaration && isPrivateIdentifierPropertyDeclaration(s.valueDeclaration) && hasSyntacticModifier(s.valueDeclaration, ModifierFlags.Static); } function resolveDeclaredMembers(type: InterfaceType): InterfaceTypeWithDeclaredMembers { if (!(type).declaredProperties) { const symbol = type.symbol; const members = getMembersOfSymbol(symbol); (type).declaredProperties = getNamedMembers(members); // Start with signatures at empty array in case of recursive types (type).declaredCallSignatures = emptyArray; (type).declaredConstructSignatures = emptyArray; (type).declaredCallSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call)); (type).declaredConstructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New)); (type).declaredStringIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.String); (type).declaredNumberIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.Number); } return type; } /** * Indicates whether a type can be used as a property name. */ function isTypeUsableAsPropertyName(type: Type): type is StringLiteralType | NumberLiteralType | UniqueESSymbolType { return !!(type.flags & TypeFlags.StringOrNumberLiteralOrUnique); } /** * 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 { if (!isComputedPropertyName(node) && !isElementAccessExpression(node)) { return false; } const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression; return isEntityNameExpression(expr) && isTypeUsableAsPropertyName(isComputedPropertyName(node) ? checkComputedPropertyName(node) : checkExpressionCached(expr)); } 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); } /** * Indicates whether a declaration has a dynamic name that cannot be late-bound. */ function hasNonBindableDynamicName(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); } /** * Gets the symbolic name for a member from its type. */ function getPropertyNameFromType(type: StringLiteralType | NumberLiteralType | UniqueESSymbolType): __String { if (type.flags & TypeFlags.UniqueESSymbol) { return (type).escapedName; } if (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { return escapeLeadingUnderscores("" + (type).value); } return Debug.fail(); } /** * 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 { symbol.declarations.push(member); } if (symbolFlags & SymbolFlags.Value) { if (!symbol.valueDeclaration || symbol.valueDeclaration.kind !== member.kind) { symbol.valueDeclaration = 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: UnderscoreEscapedMap, 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 a late-bound member has the same name as an early-bound member, // or if we have another early-bound symbol declaration with the same name and // conflicting flags. const earlySymbol = earlySymbols && earlySymbols.get(memberName); if (lateSymbol.flags & getExcludedSymbolFlags(symbolFlags) || earlySymbol) { // 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.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 getResolvedMembersOrExportsOfSymbol(symbol: Symbol, resolutionKind: MembersOrExportsResolutionKind): UnderscoreEscapedMap { const links = getSymbolLinks(symbol); if (!links[resolutionKind]) { const isStatic = resolutionKind === MembersOrExportsResolutionKind.resolvedExports; const earlySymbols = !isStatic ? symbol.members : symbol.flags & SymbolFlags.Module ? getExportsOfModuleWorker(symbol) : 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 UnderscoreEscapedMap; for (const decl of symbol.declarations || emptyArray) { const members = getMembersOfDeclaration(decl); if (members) { for (const member of members) { if (isStatic === hasStaticModifier(member) && hasLateBindableName(member)) { lateBindMember(symbol, earlySymbols, lateSymbols, member); } } } } const assignments = 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 && hasLateBindableName(member)) { lateBindMember(symbol, earlySymbols, lateSymbols, member); } } } links[resolutionKind] = combineSymbolTables(earlySymbols, lateSymbols) || 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).target; const typeArguments = getTypeArguments(type); if (length(target.typeParameters) === length(typeArguments)) { const ref = createTypeReference(target, concatenate(typeArguments, [thisArgument || target.thisType!])); return needApparentType ? getApparentType(ref) : ref; } } else if (type.flags & TypeFlags.Intersection) { return getIntersectionType(map((type).types, t => getTypeWithThisArgument(t, thisArgument, needApparentType))); } 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[] | undefined; let stringIndexInfo: IndexInfo | undefined; let numberIndexInfo: IndexInfo | undefined; if (rangeEquals(typeParameters, typeArguments, 0, typeParameters.length)) { members = source.symbol ? getMembersOfSymbol(source.symbol) : createSymbolTable(source.declaredProperties); callSignatures = source.declaredCallSignatures; constructSignatures = source.declaredConstructSignatures; stringIndexInfo = source.declaredStringIndexInfo; numberIndexInfo = source.declaredNumberIndexInfo; } else { mapper = createTypeMapper(typeParameters, typeArguments); members = createInstantiatedSymbolTable(source.declaredProperties, mapper, /*mappingThisOnly*/ typeParameters.length === 1); callSignatures = instantiateSignatures(source.declaredCallSignatures, mapper); constructSignatures = instantiateSignatures(source.declaredConstructSignatures, mapper); stringIndexInfo = instantiateIndexInfo(source.declaredStringIndexInfo, mapper); numberIndexInfo = instantiateIndexInfo(source.declaredNumberIndexInfo, mapper); } const baseTypes = getBaseTypes(source); if (baseTypes.length) { if (source.symbol && members === getMembersOfSymbol(source.symbol)) { members = createSymbolTable(source.declaredProperties); } setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); 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)); if (!stringIndexInfo) { stringIndexInfo = instantiatedBaseType === anyType ? createIndexInfo(anyType, /*isReadonly*/ false) : getIndexInfoOfType(instantiatedBaseType, IndexKind.String); } numberIndexInfo = numberIndexInfo || getIndexInfoOfType(instantiatedBaseType, IndexKind.Number); } } setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); } 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.unionSignatures = 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.unionSignatures = sig.unionSignatures; return result; } function createUnionSignature(signature: Signature, unionSignatures: Signature[]) { const result = cloneSignature(signature); result.unionSignatures = unionSignatures; 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 restType = getTypeOfSymbol(sig.parameters[restIndex]); if (isTupleType(restType)) { return [expandSignatureParametersWithTupleMembers(restType, restIndex)]; } 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)); } } return [sig.parameters]; function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number) { const elementTypes = getTypeArguments(restType); const associatedNames = restType.target.labeledElementDeclarations; 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 tupleLabelName = !!associatedNames && getTupleElementLabel(associatedNames[i]); const name = tupleLabelName || 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.type = flags & ElementFlags.Rest ? createArrayType(t) : t; return symbol; }); return concatenate(sig.parameters.slice(0, restIndex), restParams); } } function getDefaultConstructSignatures(classType: InterfaceType): Signature[] { const baseConstructorType = getBaseConstructorTypeOfClass(classType); const baseSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); if (baseSignatures.length === 0) { return [createSignature(undefined, classType.localTypeParameters, undefined, emptyArray, classType, /*resolvedTypePredicate*/ undefined, 0, 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 <= typeParamCount) { const sig = typeParamCount ? createSignatureInstantiation(baseSig, fillMissingTypeArguments(typeArguments, baseSig.typeParameters, minTypeArgumentCount, isJavaScript)) : cloneSignature(baseSig); sig.typeParameters = classType.localTypeParameters; sig.resolvedReturnType = classType; result.push(sig); } } return result; } function findMatchingSignature(signatureList: readonly Signature[], signature: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean): Signature | undefined { for (const s of signatureList) { if (compareSignaturesIdentical(s, signature, partialMatch, ignoreThisTypes, ignoreReturnTypes, partialMatch ? compareTypesSubtypeOf : compareTypesIdentical)) { return s; } } } function findMatchingSignatures(signatureLists: readonly (readonly Signature[])[], signature: Signature, listIndex: number): Signature[] | undefined { if (signature.typeParameters) { // We require an exact match for generic signatures, so we only return signatures from the first // signature list and only if they have exact matches in the other signature lists. if (listIndex > 0) { return undefined; } for (let i = 1; i < signatureLists.length; i++) { if (!findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false)) { return undefined; } } return [signature]; } let result: Signature[] | undefined; for (let i = 0; i < signatureLists.length; i++) { // Allow matching non-generic signatures to have excess parameters and different return types. // Prefer matching this types if possible. const match = i === listIndex ? signature : findMatchingSignature(signatureLists[i], signature, /*partialMatch*/ true, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ true); if (!match) { return undefined; } result = appendIfUnique(result, match); } return result; } // The signatures of a union type are those signatures that are present in each of the constituent types. // Generic signatures must match exactly, but non-generic signatures are allowed to have extra optional // parameters and may differ in return types. When signatures differ in return types, the resulting return // type is the union of the constituent return types. function getUnionSignatures(signatureLists: readonly (readonly Signature[])[]): Signature[] { let result: Signature[] | undefined; let indexWithLengthOverOne: number | undefined; for (let i = 0; i < signatureLists.length; i++) { if (signatureLists[i].length === 0) return emptyArray; if (signatureLists[i].length > 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) ? undefined : map(results, sig => combineSignaturesOfUnionMembers(sig, signature)); if (!results) { break; } } } result = results; } return result || emptyArray; } function combineUnionThisParam(left: Symbol | undefined, right: Symbol | 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), getTypeOfSymbol(right)]); return createSymbolWithType(left, thisType); } function combineUnionParameters(left: Signature, right: Signature) { const leftCount = getParameterCount(left); const rightCount = getParameterCount(right); const longest = leftCount >= rightCount ? left : right; const shorter = longest === left ? right : left; const longestCount = longest === left ? leftCount : rightCount; const eitherHasEffectiveRest = (hasEffectiveRestParameter(left) || hasEffectiveRestParameter(right)); const needsExtraRestElement = eitherHasEffectiveRest && !hasEffectiveRestParameter(longest); const params = new Array(longestCount + (needsExtraRestElement ? 1 : 0)); for (let i = 0; i < longestCount; i++) { const longestParamType = tryGetTypeAtPosition(longest, i)!; const shorterParamType = tryGetTypeAtPosition(shorter, i) || unknownType; const unionParamType = getIntersectionType([longestParamType, shorterParamType]); const isRestParam = eitherHasEffectiveRest && !needsExtraRestElement && i === (longestCount - 1); const isOptional = i >= getMinArgumentCount(longest) && i >= getMinArgumentCount(shorter); const leftName = i >= leftCount ? undefined : getParameterNameAtPosition(left, i); const rightName = i >= rightCount ? undefined : getParameterNameAtPosition(right, i); const paramName = leftName === rightName ? leftName : !leftName ? rightName : !rightName ? leftName : undefined; const paramSymbol = createSymbol( SymbolFlags.FunctionScopedVariable | (isOptional && !isRestParam ? SymbolFlags.Optional : 0), paramName || `arg${i}` as __String ); paramSymbol.type = isRestParam ? createArrayType(unionParamType) : unionParamType; params[i] = paramSymbol; } if (needsExtraRestElement) { const restParamSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "args" as __String); restParamSymbol.type = createArrayType(getTypeAtPosition(shorter, longestCount)); params[longestCount] = restParamSymbol; } return params; } function combineSignaturesOfUnionMembers(left: Signature, right: Signature): Signature { const declaration = left.declaration; const params = combineUnionParameters(left, right); const thisParam = combineUnionThisParam(left.thisParameter, right.thisParameter); const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount); const result = createSignature( declaration, left.typeParameters || right.typeParameters, thisParam, params, /*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined, minArgCount, (left.flags | right.flags) & SignatureFlags.PropagatingFlags ); result.unionSignatures = concatenate(left.unionSignatures || [left], [right]); return result; } function getUnionIndexInfo(types: readonly Type[], kind: IndexKind): IndexInfo | undefined { const indexTypes: Type[] = []; let isAnyReadonly = false; for (const type of types) { const indexInfo = getIndexInfoOfType(getApparentType(type), kind); if (!indexInfo) { return undefined; } indexTypes.push(indexInfo.type); isAnyReadonly = isAnyReadonly || indexInfo.isReadonly; } return createIndexInfo(getUnionType(indexTypes, UnionReduction.Subtype), isAnyReadonly); } 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 stringIndexInfo = getUnionIndexInfo(type.types, IndexKind.String); const numberIndexInfo = getUnionIndexInfo(type.types, IndexKind.Number); setStructuredTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); } 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 intersectIndexInfos(info1: IndexInfo | undefined, info2: IndexInfo | undefined): IndexInfo | undefined { return !info1 ? info2 : !info2 ? info1 : createIndexInfo( getIntersectionType([info1.type, info2.type]), info1.isReadonly && info2.isReadonly); } function unionSpreadIndexInfos(info1: IndexInfo | undefined, info2: IndexInfo | undefined): IndexInfo | undefined { return info1 && info2 && createIndexInfo( getUnionType([info1.type, info2.type]), info1.isReadonly || info2.isReadonly); } 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 < types.length; i++) { if (i === index) { mixedTypes.push(type); } else if (mixinFlags[i]) { mixedTypes.push(getReturnTypeOfSignature(getSignaturesOfType(types[i], SignatureKind.Construct)[0])); } } return getIntersectionType(mixedTypes); } function resolveIntersectionTypeMembers(type: IntersectionType) { // The members and properties collections are empty for intersection types. To get all properties of an // intersection type use getPropertiesOfType (only the language service uses this). let callSignatures: Signature[] | undefined; let constructSignatures: Signature[] | undefined; let stringIndexInfo: IndexInfo | undefined; let numberIndexInfo: IndexInfo | undefined; const types = type.types; const mixinFlags = findMixins(types); const mixinCount = countWhere(mixinFlags, (b) => b); for (let i = 0; i < types.length; i++) { const t = type.types[i]; // When an intersection type contains mixin constructor types, the construct signatures from // those types are discarded and their return types are mixed into the return types of all // other construct signatures in the intersection type. For example, the intersection type // '{ new(...args: any[]) => 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)); stringIndexInfo = intersectIndexInfos(stringIndexInfo, getIndexInfoOfType(t, IndexKind.String)); numberIndexInfo = intersectIndexInfos(numberIndexInfo, getIndexInfoOfType(t, IndexKind.Number)); } setStructuredTypeMembers(type, emptySymbols, callSignatures || emptyArray, constructSignatures || emptyArray, stringIndexInfo, numberIndexInfo); } 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; } /** * Converts an AnonymousType to a ResolvedType. */ function resolveAnonymousTypeMembers(type: AnonymousType) { const symbol = getMergedSymbol(type.symbol); if (type.target) { setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined); const members = createInstantiatedSymbolTable(getPropertiesOfObjectType(type.target), type.mapper!, /*mappingThisOnly*/ false); const callSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Call), type.mapper!); const constructSignatures = instantiateSignatures(getSignaturesOfType(type.target, SignatureKind.Construct), type.mapper!); const stringIndexInfo = instantiateIndexInfo(getIndexInfoOfType(type.target, IndexKind.String), type.mapper!); const numberIndexInfo = instantiateIndexInfo(getIndexInfoOfType(type.target, IndexKind.Number), type.mapper!); setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); } else if (symbol.flags & SymbolFlags.TypeLiteral) { setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined); const members = getMembersOfSymbol(symbol); const callSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.Call)); const constructSignatures = getSignaturesOfSymbol(members.get(InternalSymbolName.New)); const stringIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.String); const numberIndexInfo = getIndexInfoOfSymbol(symbol, IndexKind.Number); setStructuredTypeMembers(type, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); } else { // Combinations of function, class, enum and module let members = emptySymbols; let stringIndexInfo: IndexInfo | undefined; if (symbol.exports) { members = getExportsOfSymbol(symbol); if (symbol === globalThisSymbol) { const varsOnly = new Map() as SymbolTable; members.forEach(p => { if (!(p.flags & SymbolFlags.BlockScoped)) { varsOnly.set(p.escapedName, p); } }); members = varsOnly; } } setStructuredTypeMembers(type, members, emptyArray, emptyArray, undefined, undefined); if (symbol.flags & SymbolFlags.Class) { const classType = getDeclaredTypeOfClassOrInterface(symbol); const baseConstructorType = getBaseConstructorTypeOfClass(classType); if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.TypeVariable)) { members = createSymbolTable(getNamedMembers(members)); addInheritedMembers(members, getPropertiesOfType(baseConstructorType)); } else if (baseConstructorType === anyType) { stringIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false); } } const numberIndexInfo = symbol.flags & SymbolFlags.Enum && (getDeclaredTypeOfSymbol(symbol).flags & TypeFlags.Enum || some(type.properties, prop => !!(getTypeOfSymbol(prop).flags & TypeFlags.NumberLike))) ? enumNumberIndexInfo : undefined; setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); // 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; } } } function resolveReverseMappedTypeMembers(type: ReverseMappedType) { const indexInfo = getIndexInfoOfType(type.source, IndexKind.String); const modifiers = getMappedTypeModifiers(type.mappedType); const readonlyMask = modifiers & MappedTypeModifiers.IncludeReadonly ? false : true; const optionalMask = modifiers & MappedTypeModifiers.IncludeOptional ? 0 : SymbolFlags.Optional; const stringIndexInfo = indexInfo && createIndexInfo(inferReverseMappedType(indexInfo.type, type.mappedType, type.constraintType), readonlyMask && indexInfo.isReadonly); const members = createSymbolTable(); for (const prop of getPropertiesOfType(type.source)) { 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.nameType = getSymbolLinks(prop).nameType; inferredProp.propertyType = getTypeOfSymbol(prop); inferredProp.mappedType = type.mappedType; inferredProp.constraintType = type.constraintType; members.set(prop.escapedName, inferredProp); } setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined); } // 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).type); return isGenericTupleType(t) ? getKnownKeysOfTupleType(t) : getIndexType(t); } if (type.flags & TypeFlags.Conditional) { if ((type).root.isDistributive) { const checkType = (type).checkType; const constraint = getLowerBoundOfKeyType(checkType); if (constraint !== checkType) { return getConditionalTypeInstantiation(type, prependTypeMapping((type).root.checkType, constraint, (type).mapper)); } } return type; } if (type.flags & TypeFlags.Union) { return getUnionType(sameMap((type).types, getLowerBoundOfKeyType)); } if (type.flags & TypeFlags.Intersection) { return getIntersectionType(sameMap((type).types, getLowerBoundOfKeyType)); } return type; } /** Resolve the members of a mapped type { [P in K]: T } */ function resolveMappedTypeMembers(type: MappedType) { const members: SymbolTable = createSymbolTable(); let stringIndexInfo: IndexInfo | undefined; let numberIndexInfo: IndexInfo | undefined; // Resolve upfront such that recursive references see an empty object type. setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined); // 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 nameType = getNameTypeFromMappedType(type.target || type); const templateType = getTemplateTypeFromMappedType(type.target || type); const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' const templateModifiers = getMappedTypeModifiers(type); const include = keyofStringsOnly ? TypeFlags.StringLiteral : TypeFlags.StringOrNumberLiteralOrUnique; if (isMappedTypeWithKeyofConstraintDeclaration(type)) { // We have a { [P in keyof T]: X } for (const prop of getPropertiesOfType(modifiersType)) { addMemberForKeyType(getLiteralTypeFromProperty(prop, include)); } if (modifiersType.flags & TypeFlags.Any || getIndexInfoOfType(modifiersType, IndexKind.String)) { addMemberForKeyType(stringType); } if (!keyofStringsOnly && getIndexInfoOfType(modifiersType, IndexKind.Number)) { addMemberForKeyType(numberType); } } else { forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType); } setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); 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.nameType = getUnionType([existingProp.nameType!, propNameType]); existingProp.keyType = getUnionType([existingProp.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 prop = createSymbol(SymbolFlags.Property | (isOptional ? SymbolFlags.Optional : 0), propName, CheckFlags.Mapped | (isReadonly ? CheckFlags.Readonly : 0) | (stripOptional ? CheckFlags.StripOptional : 0)); prop.mappedType = type; prop.nameType = propNameType; prop.keyType = keyType; if (modifiersProp) { prop.syntheticOrigin = modifiersProp; prop.declarations = modifiersProp.declarations; } members.set(propName, prop); } } else if (propNameType.flags & (TypeFlags.Any | TypeFlags.String | TypeFlags.Number | TypeFlags.Enum)) { const propType = instantiateType(templateType, appendTypeMapping(type.mapper, typeParameter, keyType)); if (propNameType.flags & (TypeFlags.Any | TypeFlags.String)) { stringIndexInfo = createIndexInfo(stringIndexInfo ? getUnionType([stringIndexInfo.type, propType]) : propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly)); } else { numberIndexInfo = createIndexInfo(numberIndexInfo ? getUnionType([numberIndexInfo.type, propType]) : propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly)); } } } } function getTypeOfMappedSymbol(symbol: MappedSymbol) { if (!symbol.type) { const mappedType = symbol.mappedType; if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { mappedType.containsError = true; return errorType; } const templateType = getTemplateTypeFromMappedType(mappedType.target || mappedType); const mapper = appendTypeMapping(mappedType.mapper, getTypeParameterFromMappedType(mappedType), symbol.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) : symbol.checkFlags & CheckFlags.StripOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) : propType; if (!popTypeResolution()) { error(currentNode, Diagnostics.Type_of_property_0_circularly_references_itself_in_mapped_type_1, symbolToString(symbol), typeToString(mappedType)); type = errorType; } symbol.type = type; } return symbol.type; } function getTypeParameterFromMappedType(type: MappedType) { return type.typeParameter || (type.typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(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), !!(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).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)).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); const constraint = getConstraintTypeFromMappedType(declaredType); const extendedConstraint = constraint && constraint.flags & TypeFlags.TypeParameter ? getConstraintOfTypeParameter(constraint) : constraint; type.modifiersType = extendedConstraint && extendedConstraint.flags & TypeFlags.Index ? instantiateType((extendedConstraint).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); } function getMappedTypeOptionality(type: MappedType): number { const modifiers = getMappedTypeModifiers(type); return modifiers & MappedTypeModifiers.ExcludeOptional ? -1 : modifiers & MappedTypeModifiers.IncludeOptional ? 1 : 0; } function getCombinedMappedTypeOptionality(type: MappedType): number { const optionality = getMappedTypeOptionality(type); const modifiersType = getModifiersTypeFromMappedType(type); return optionality || (isGenericMappedType(modifiersType) ? getMappedTypeOptionality(modifiersType) : 0); } function isPartialMappedType(type: Type) { return !!(getObjectFlags(type) & ObjectFlags.Mapped && getMappedTypeModifiers(type) & MappedTypeModifiers.IncludeOptional); } function isGenericMappedType(type: Type): type is MappedType { return !!(getObjectFlags(type) & ObjectFlags.Mapped) && isGenericIndexType(getConstraintTypeFromMappedType(type)); } function resolveStructuredTypeMembers(type: StructuredType): ResolvedType { if (!(type).members) { if (type.flags & TypeFlags.Object) { if ((type).objectFlags & ObjectFlags.Reference) { resolveTypeReferenceMembers(type); } else if ((type).objectFlags & ObjectFlags.ClassOrInterface) { resolveClassOrInterfaceMembers(type); } else if ((type).objectFlags & ObjectFlags.ReverseMapped) { resolveReverseMappedTypeMembers(type as ReverseMappedType); } else if ((type).objectFlags & ObjectFlags.Anonymous) { resolveAnonymousTypeMembers(type); } else if ((type).objectFlags & ObjectFlags.Mapped) { resolveMappedTypeMembers(type); } } else if (type.flags & TypeFlags.Union) { resolveUnionTypeMembers(type); } else if (type.flags & TypeFlags.Intersection) { resolveIntersectionTypeMembers(type); } } return type; } /** 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).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); 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); 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 && !getIndexInfoOfType(current, IndexKind.String) && !getIndexInfoOfType(current, IndexKind.Number)) { break; } } type.resolvedProperties = getNamedMembers(members); } return type.resolvedProperties; } function getPropertiesOfType(type: Type): Symbol[] { type = getReducedApparentType(type); return type.flags & TypeFlags.UnionOrIntersection ? getPropertiesOfUnionOrIntersectionType(type) : getPropertiesOfObjectType(type); } function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean { const list = obj.properties as NodeArray; return list.some(property => { const nameType = 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) : type.flags & TypeFlags.IndexedAccess ? getConstraintOfIndexedAccess(type) : type.flags & TypeFlags.Conditional ? getConstraintOfConditionalType(type) : getBaseConstraintOfType(type); } function getConstraintOfTypeParameter(typeParameter: TypeParameter): Type | undefined { return hasNonCircularBaseConstraint(typeParameter) ? getConstraintFromTypeParameter(typeParameter) : undefined; } 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) { const indexConstraint = getSimplifiedTypeOrConstraint(type.indexType); if (indexConstraint && indexConstraint !== type.indexType) { const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint, type.noUncheckedIndexedAccessCandidate); if (indexedAccess) { return indexedAccess; } } const objectConstraint = getSimplifiedTypeOrConstraint(type.objectType); if (objectConstraint && objectConstraint !== type.objectType) { return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType, type.noUncheckedIndexedAccessCandidate); } 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 { // 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)); if (!(instantiated.flags & TypeFlags.Never)) { return instantiated; } } } 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) { 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) { constraints = append(constraints, t); } } } return getIntersectionType(constraints); } return undefined; } function getBaseConstraintOfType(type: Type): Type | undefined { if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) { const constraint = getResolvedBaseConstraint(type); return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined; } return type.flags & TypeFlags.Index ? keyofConstraintType : 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; } let nonTerminating = false; const stack: Type[] = []; return type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type); function getImmediateBaseConstraint(t: Type): Type { if (!t.immediateBaseConstraint) { if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) { return circularConstraintType; } if (constraintDepth >= 50) { // We have reached 50 recursive invocations of getImmediateBaseConstraint and there is a // very high likelihood we're dealing with an infinite generic type that perpetually generates // new type identities as we descend into it. We stop the recursion here and mark this type // and the outer types as having circular constraints. tracing.instant(tracing.Phase.Check, "getImmediateBaseConstraint_DepthLimit", { typeId: t.id, originalTypeId: type.id, depth: constraintDepth }); error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); nonTerminating = true; return t.immediateBaseConstraint = noConstraintType; } let result; if (!isDeeplyNestedType(t, stack, stack.length)) { stack.push(t); constraintDepth++; result = computeBaseConstraint(getSimplifiedType(t, /*writing*/ false)); constraintDepth--; stack.pop(); } if (!popTypeResolution()) { if (t.flags & TypeFlags.TypeParameter) { const errorNode = getConstraintDeclaration(t); if (errorNode) { const diagnostic = error(errorNode, Diagnostics.Type_parameter_0_has_a_circular_constraint, typeToString(t)); if (currentNode && !isNodeDescendantOf(errorNode, currentNode) && !isNodeDescendantOf(currentNode, errorNode)) { addRelatedInfo(diagnostic, createDiagnosticForNode(currentNode, Diagnostics.Circularity_originates_in_type_at_this_location)); } } } result = circularConstraintType; } if (nonTerminating) { result = circularConstraintType; } t.immediateBaseConstraint = result || noConstraintType; } return t.immediateBaseConstraint; } function getBaseConstraint(t: Type): Type | undefined { const c = getImmediateBaseConstraint(t); return c !== noConstraintType && c !== circularConstraintType ? c : undefined; } function computeBaseConstraint(t: Type): Type | undefined { if (t.flags & TypeFlags.TypeParameter) { const constraint = getConstraintFromTypeParameter(t); return (t as TypeParameter).isThisType || !constraint ? constraint : getBaseConstraint(constraint); } if (t.flags & TypeFlags.UnionOrIntersection) { const types = (t).types; const baseTypes: Type[] = []; for (const type of types) { const baseType = getBaseConstraint(type); if (baseType) { baseTypes.push(baseType); } } return t.flags & TypeFlags.Union && baseTypes.length === types.length ? getUnionType(baseTypes) : t.flags & TypeFlags.Intersection && baseTypes.length ? getIntersectionType(baseTypes) : undefined; } if (t.flags & TypeFlags.Index) { return keyofConstraintType; } if (t.flags & TypeFlags.TemplateLiteral) { const types = (t).types; const constraints = mapDefined(types, getBaseConstraint); return constraints.length === types.length ? getTemplateLiteralType((t).texts, constraints) : stringType; } if (t.flags & TypeFlags.StringMapping) { const constraint = getBaseConstraint((t).type); return constraint ? getStringMappingType((t).symbol, constraint) : stringType; } if (t.flags & TypeFlags.IndexedAccess) { const baseObjectType = getBaseConstraint((t).objectType); const baseIndexType = getBaseConstraint((t).indexType); const baseIndexedAccess = baseObjectType && baseIndexType && getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, (t).noUncheckedIndexedAccessCandidate); return baseIndexedAccess && getBaseConstraint(baseIndexedAccess); } if (t.flags & TypeFlags.Conditional) { const constraint = getConstraintFromConditionalType(t); constraintDepth++; // Penalize repeating conditional types (this captures the recursion within getConstraintFromConditionalType and carries it forward) const result = constraint && getBaseConstraint(constraint); constraintDepth--; return result; } if (t.flags & TypeFlags.Substitution) { return getBaseConstraint((t).substitute); } return t; } } function getApparentTypeOfIntersectionType(type: IntersectionType) { return type.resolvedApparentType || (type.resolvedApparentType = getTypeWithThisArgument(type, type, /*apparentType*/ 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) { const typeVariable = getHomomorphicTypeVariable(type); if (typeVariable && !type.declaration.nameType) { const constraint = getConstraintOfTypeParameter(typeVariable); if (constraint && (isArrayType(constraint) || isTupleType(constraint))) { return instantiateType(type, prependTypeMapping(typeVariable, constraint, type.mapper)); } } return type; } /** * 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; return getObjectFlags(t) & ObjectFlags.Mapped ? getApparentTypeOfMappedType(t) : t.flags & TypeFlags.Intersection ? getApparentTypeOfIntersectionType(t) : t.flags & TypeFlags.StringLike ? globalStringType : t.flags & TypeFlags.NumberLike ? globalNumberType : t.flags & TypeFlags.BigIntLike ? getGlobalBigIntType(/*reportErrors*/ languageVersion >= ScriptTarget.ES2020) : t.flags & TypeFlags.BooleanLike ? globalBooleanType : t.flags & TypeFlags.ESSymbolLike ? getGlobalESSymbolType(/*reportErrors*/ languageVersion >= ScriptTarget.ES2015) : t.flags & TypeFlags.NonPrimitive ? emptyObjectType : t.flags & TypeFlags.Index ? keyofConstraintType : 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): Symbol | undefined { let singleProp: Symbol | undefined; let propSet: ESMap | 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 = isUnion ? SymbolFlags.None : SymbolFlags.Optional; let syntheticFlag = CheckFlags.SyntheticMethod; let checkFlags = 0; for (const current of containingType.types) { const type = getApparentType(current); if (!(type === errorType || type.flags & TypeFlags.Never)) { const prop = getPropertyOfType(type, name); const modifiers = prop ? getDeclarationModifierFlagsFromSymbol(prop) : 0; if (prop) { if (isUnion) { optionalFlag |= (prop.flags & SymbolFlags.Optional); } else { optionalFlag &= prop.flags; } if (!singleProp) { singleProp = prop; } else if (prop !== singleProp) { if (!propSet) { propSet = new Map(); propSet.set(getSymbolId(singleProp), singleProp); } const id = getSymbolId(prop); if (!propSet.has(id)) { propSet.set(id, prop); } } checkFlags |= (isReadonlySymbol(prop) ? CheckFlags.Readonly : 0) | (!(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) && (isNumericLiteralName(name) && getIndexInfoOfType(type, IndexKind.Number) || getIndexInfoOfType(type, IndexKind.String)); if (indexInfo) { checkFlags |= CheckFlags.WritePartial | (indexInfo.isReadonly ? CheckFlags.Readonly : 0); indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type); } else if (isObjectLiteralType(type)) { checkFlags |= CheckFlags.WritePartial; indexTypes = append(indexTypes, undefinedType); } else { checkFlags |= CheckFlags.ReadPartial; } } } } if (!singleProp || isUnion && (propSet || checkFlags & CheckFlags.Partial) && checkFlags & (CheckFlags.ContainsPrivate | CheckFlags.ContainsProtected)) { // 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) { 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 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; } else if (type !== firstType) { checkFlags |= CheckFlags.HasNonUniformType; } if (isLiteralType(type)) { checkFlags |= CheckFlags.HasLiteralType; } if (type.flags & TypeFlags.Never) { checkFlags |= CheckFlags.HasNeverType; } propTypes.push(type); } addRange(propTypes, indexTypes); const result = createSymbol(SymbolFlags.Property | optionalFlag, name, syntheticFlag | checkFlags); result.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.nameType = nameType; if (propTypes.length > 2) { // When `propTypes` has the potential to explode in size when normalized, defer normalization until absolutely needed result.checkFlags |= CheckFlags.DeferredType; result.deferralParent = containingType; result.deferralConstituents = propTypes; } else { result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes); } 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): Symbol | undefined { const properties = type.propertyCache || (type.propertyCache = createSymbolTable()); let property = properties.get(name); if (!property) { property = createUnionOrIntersectionProperty(type, name); if (property) { properties.set(name, property); } } return property; } function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: __String): Symbol | undefined { const property = getUnionOrIntersectionProperty(type, name); // 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).objectFlags & ObjectFlags.ContainsIntersections) { return (type).resolvedReducedType || ((type).resolvedReducedType = getReducedUnionType(type)); } else if (type.flags & TypeFlags.Intersection) { if (!((type).objectFlags & ObjectFlags.IsNeverIntersectionComputed)) { (type).objectFlags |= ObjectFlags.IsNeverIntersectionComputed | (some(getPropertiesOfUnionOrIntersectionType(type), isNeverReducedProperty) ? ObjectFlags.IsNeverIntersection : 0); } return (type).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).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); } function elaborateNeverIntersection(errorInfo: DiagnosticMessageChain | undefined, type: Type) { if (getObjectFlags(type) & ObjectFlags.IsNeverIntersection) { const neverProp = find(getPropertiesOfUnionOrIntersectionType(type), 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), 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): Symbol | undefined { type = getReducedApparentType(type); if (type.flags & TypeFlags.Object) { const resolved = resolveStructuredTypeMembers(type); const symbol = resolved.members.get(name); if (symbol && symbolIsValue(symbol)) { return symbol; } const functionType = resolved === anyFunctionType ? globalFunctionType : resolved.callSignatures.length ? globalCallableFunctionType : resolved.constructSignatures.length ? globalNewableFunctionType : undefined; if (functionType) { const symbol = getPropertyOfObjectType(functionType, name); if (symbol) { return symbol; } } return getPropertyOfObjectType(globalObjectType, name); } if (type.flags & TypeFlags.UnionOrIntersection) { return getPropertyOfUnionOrIntersectionType(type, name); } return undefined; } function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): readonly Signature[] { if (type.flags & TypeFlags.StructuredType) { const resolved = resolveStructuredTypeMembers(type); 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[] { return getSignaturesOfStructuredType(getReducedApparentType(type), kind); } function getIndexInfoOfStructuredType(type: Type, kind: IndexKind): IndexInfo | undefined { if (type.flags & TypeFlags.StructuredType) { const resolved = resolveStructuredTypeMembers(type); return kind === IndexKind.String ? resolved.stringIndexInfo : resolved.numberIndexInfo; } } function getIndexTypeOfStructuredType(type: Type, kind: IndexKind): Type | undefined { const info = getIndexInfoOfStructuredType(type, kind); return info && info.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, kind: IndexKind): IndexInfo | undefined { return getIndexInfoOfStructuredType(getReducedApparentType(type), kind); } // 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, kind: IndexKind): Type | undefined { return getIndexTypeOfStructuredType(getReducedApparentType(type), kind); } function getImplicitIndexTypeOfType(type: Type, kind: IndexKind): Type | undefined { if (isObjectTypeWithInferableIndex(type)) { const propTypes: Type[] = []; for (const prop of getPropertiesOfType(type)) { if (kind === IndexKind.String || isNumericLiteralName(prop.escapedName)) { propTypes.push(getTypeOfSymbol(prop)); } } if (kind === IndexKind.String) { append(propTypes, getIndexTypeOfType(type, IndexKind.Number)); } if (propTypes.length) { return getUnionType(propTypes); } } return undefined; } // Return list of type parameters with duplicates removed (duplicate identifier errors are generated in the actual // type checking functions). function getTypeParametersFromDeclaration(declaration: DeclarationWithTypeParameters): TypeParameter[] | undefined { let result: TypeParameter[] | undefined; for (const node of getEffectiveTypeParameterDeclarations(declaration)) { result = appendIfUnique(result, getDeclaredTypeOfTypeParameter(node.symbol)); } return result; } function symbolsToArray(symbols: SymbolTable): Symbol[] { const result: Symbol[] = []; symbols.forEach((symbol, id) => { if (!isReservedMemberName(id)) { result.push(symbol); } }); return result; } function isJSDocOptionalParameter(node: ParameterDeclaration) { return isInJSFile(node) && ( // node.type should only be a JSDocOptionalType when node is a parameter of a JSDocFunctionType node.type && node.type.kind === SyntaxKind.JSDocOptionalType || getJSDocParameterTags(node).some(({ isBracketed, typeExpression }) => isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType)); } 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 isOptionalParameter(node: ParameterDeclaration | JSDocParameterTag | JSDocPropertyTag) { if (hasQuestionToken(node) || isOptionalJSDocPropertyLikeTag(node) || isJSDocOptionalParameter(node)) { return true; } 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) >= iife.arguments.length; } return false; } function isOptionalJSDocPropertyLikeTag(node: Node): node is JSDocPropertyLikeTag { if (!isJSDocPropertyLikeTag(node)) { return false; } const { isBracketed, typeExpression } = node; return isBracketed || !!typeExpression && typeExpression.type.kind === SyntaxKind.JSDocOptionalType; } 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 < typeParameters.length; i++) { if (!hasTypeParameterDefault(typeParameters[i])) { minTypeArgumentCount = i + 1; } } } return minTypeArgumentCount; } /** * Fill in default types for unsupplied type arguments. If `typeArguments` is undefined * when a default type is supplied, a new array will be created and returned. * * @param typeArguments The supplied type arguments. * @param typeParameters The requested type parameters. * @param minTypeArgumentCount The minimum number of required type arguments. */ function fillMissingTypeArguments(typeArguments: readonly Type[], typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[]; function fillMissingTypeArguments(typeArguments: readonly Type[] | undefined, typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[] | undefined; function fillMissingTypeArguments(typeArguments: readonly Type[] | undefined, typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean) { const numTypeParameters = length(typeParameters); if (!numTypeParameters) { return []; } const numTypeArguments = length(typeArguments); if (isJavaScriptImplicitAny || (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters)) { const result = typeArguments ? typeArguments.slice() : []; // Map invalid forward references in default types to the error type for (let i = numTypeArguments; i < numTypeParameters; i++) { result[i] = errorType; } const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny); for (let i = numTypeArguments; i < numTypeParameters; i++) { let defaultType = getDefaultFromTypeParameter(typeParameters![i]); if (isJavaScriptImplicitAny && defaultType && (isTypeIdenticalTo(defaultType, unknownType) || isTypeIdenticalTo(defaultType, emptyObjectType))) { defaultType = anyType; } result[i] = defaultType ? instantiateType(defaultType, createTypeMapper(typeParameters!, result)) : baseDefaultType; } result.length = typeParameters!.length; return result; } return typeArguments && typeArguments.slice(); } function getSignatureFromDeclaration(declaration: SignatureDeclaration | JSDocSignature): Signature { const links = getNodeLinks(declaration); if (!links.resolvedSignature) { const parameters: Symbol[] = []; let flags = SignatureFlags.None; let minArgumentCount = 0; let thisParameter: Symbol | undefined; let hasThisParameter = false; const iife = getImmediatelyInvokedFunctionExpression(declaration); const isJSConstructSignature = isJSDocConstructSignature(declaration); const isUntypedSignatureInJSFile = !iife && isInJSFile(declaration) && isValueSignatureDeclaration(declaration) && !hasJSDocParameterTags(declaration) && !getJSDocType(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 < declaration.parameters.length; i++) { const param = declaration.parameters[i]; let paramSymbol = param.symbol; const type = isJSDocParameterTag(param) ? (param.typeExpression && param.typeExpression.type) : param.type; // Include parameter symbol instead of property symbol in the signature if (paramSymbol && !!(paramSymbol.flags & SymbolFlags.Property) && !isBindingPattern(param.name)) { const resolvedSymbol = resolveName(param, paramSymbol.escapedName, SymbolFlags.Value, undefined, undefined, /*isUse*/ false); paramSymbol = resolvedSymbol!; } if (i === 0 && paramSymbol.escapedName === InternalSymbolName.This) { hasThisParameter = true; thisParameter = param.symbol; } else { parameters.push(paramSymbol); } if (type && type.kind === SyntaxKind.LiteralType) { flags |= SignatureFlags.HasLiteralTypes; } // Record a new minimum argument count if this is not an optional parameter const isOptionalParameter = isOptionalJSDocPropertyLikeTag(param) || param.initializer || param.questionToken || param.dotDotDotToken || iife && parameters.length > iife.arguments.length && !type || isJSDocOptionalParameter(param); 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) && !hasNonBindableDynamicName(declaration) && (!hasThisParameter || !thisParameter)) { const otherKind = declaration.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; const other = getDeclarationOfKind(getSymbolOfNode(declaration), otherKind); if (other) { thisParameter = getAnnotatedAccessorThisParameter(other); } } const classType = declaration.kind === SyntaxKind.Constructor ? getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent).symbol)) : undefined; const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration); if (hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters)) { flags |= SignatureFlags.HasRestParameter; } 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); syntheticArgsSymbol.type = lastParamVariadicType ? createArrayType(getTypeFromTypeNode(lastParamVariadicType.type)) : 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); const signature = typeTag && typeTag.typeExpression && getSingleCallSignature(getTypeFromTypeNode(typeTag.typeExpression)); return signature && getErasedSignature(signature); } 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).escapedText === "arguments" && isExpressionNode(node); case SyntaxKind.PropertyDeclaration: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: return (node).name!.kind === SyntaxKind.ComputedPropertyName && traverse((node).name!); default: return !nodeStartsNewLexicalEnvironment(node) && !isPartOfTypeNode(node) && !!forEachChild(node, traverse); } } } function getSignaturesOfSymbol(symbol: Symbol | undefined): Signature[] { if (!symbol) return emptyArray; const result: Signature[] = []; for (let i = 0; i < symbol.declarations.length; i++) { const decl = symbol.declarations[i]; if (!isFunctionLike(decl)) continue; // Don't include signature if node is the implementation of an overloaded function. A node is considered // an implementation node if it has a body and the previous node is of the same kind and immediately // precedes the implementation node (i.e. has the same parent and ends where the implementation starts). if (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; } } result.push(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.unionSignatures) { signature.resolvedTypePredicate = getUnionTypePredicate(signature.unionSignatures) || noTypePredicate; } else { const type = signature.declaration && getEffectiveReturnTypeNode(signature.declaration); let jsdocPredicate: TypePredicate | undefined; if (!type && isInJSFile(signature.declaration)) { const jsdocSignature = getSignatureOfTypeTag(signature.declaration!); if (jsdocSignature && signature !== jsdocSignature) { jsdocPredicate = getTypePredicateOfSignature(jsdocSignature); } } signature.resolvedTypePredicate = type && isTypePredicateNode(type) ? createTypePredicateFromTypePredicateNode(type, signature) : jsdocPredicate || 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 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.unionSignatures ? getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype) : getReturnTypeFromAnnotation(signature.declaration!) || (nodeIsMissing((signature.declaration).body) ? anyType : getReturnTypeFromBody(signature.declaration)); 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; 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).symbol)); } if (isJSDocConstructSignature(declaration)) { return getTypeFromTypeNode((declaration.parameters[0] as ParameterDeclaration).type!); // TODO: GH#18217 } const typeNode = getEffectiveReturnTypeNode(declaration); if (typeNode) { return getTypeFromTypeNode(typeNode); } if (declaration.kind === SyntaxKind.GetAccessor && !hasNonBindableDynamicName(declaration)) { const jsDocType = isInJSFile(declaration) && getTypeForDeclarationFromJSDocComment(declaration); if (jsDocType) { return jsDocType; } const setter = getDeclarationOfKind(getSymbolOfNode(declaration), SyntaxKind.SetAccessor); const setterType = getAnnotatedAccessorType(setter); if (setterType) { return setterType; } } return getReturnTypeOfTypeTag(declaration); } function isResolvingReturnTypeOfSignature(signature: Signature) { return !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, IndexKind.Number); } 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 newInstantiatedSignature = cloneSignature(instantiatedSignature); newInstantiatedSignature.resolvedReturnType = getOrCreateTypeFromSignature(newReturnSignature); 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 createSignatureTypeMapper(signature: Signature, typeArguments: readonly Type[] | undefined): TypeMapper { return createTypeMapper(signature.typeParameters!, 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) { const typeEraser = createTypeEraser(typeParameters); const baseConstraints = map(typeParameters, tp => instantiateType(getBaseConstraintOfType(tp), typeEraser) || unknownType); return instantiateSignature(signature, createTypeMapper(typeParameters, baseConstraints), /*eraseTypeParameters*/ true); } return signature; } function getOrCreateTypeFromSignature(signature: Signature): ObjectType { // There are two ways to declare a construct signature, one is by declaring a class constructor // using the constructor keyword, and the other is declaring a bare construct signature in an // object type literal or interface (using the new keyword). Each way of declaring a constructor // will result in a different declaration kind. if (!signature.isolatedSignatureType) { const kind = signature.declaration ? signature.declaration.kind : SyntaxKind.Unknown; const isConstructor = kind === SyntaxKind.Constructor || kind === SyntaxKind.ConstructSignature || kind === SyntaxKind.ConstructorType; const type = createObjectType(ObjectFlags.Anonymous); type.members = emptySymbols; type.properties = emptyArray; type.callSignatures = !isConstructor ? [signature] : emptyArray; type.constructSignatures = isConstructor ? [signature] : emptyArray; signature.isolatedSignatureType = type; } return signature.isolatedSignatureType; } function getIndexSymbol(symbol: Symbol): Symbol | undefined { return symbol.members!.get(InternalSymbolName.Index); } function getIndexDeclarationOfSymbol(symbol: Symbol, kind: IndexKind): IndexSignatureDeclaration | undefined { const syntaxKind = kind === IndexKind.Number ? SyntaxKind.NumberKeyword : SyntaxKind.StringKeyword; const indexSymbol = getIndexSymbol(symbol); if (indexSymbol) { for (const decl of indexSymbol.declarations) { const node = cast(decl, isIndexSignatureDeclaration); if (node.parameters.length === 1) { const parameter = node.parameters[0]; if (parameter.type && parameter.type.kind === syntaxKind) { return node; } } } } return undefined; } function createIndexInfo(type: Type, isReadonly: boolean, declaration?: IndexSignatureDeclaration): IndexInfo { return { type, isReadonly, declaration }; } function getIndexInfoOfSymbol(symbol: Symbol, kind: IndexKind): IndexInfo | undefined { const declaration = getIndexDeclarationOfSymbol(symbol, kind); if (declaration) { return createIndexInfo(declaration.type ? getTypeFromTypeNode(declaration.type) : anyType, hasEffectiveModifier(declaration, ModifierFlags.Readonly), declaration); } return undefined; } function getConstraintDeclaration(type: TypeParameter): TypeNode | undefined { return mapDefined(filter(type.symbol && type.symbol.declarations, isTypeParameterDeclaration), getEffectiveConstraintOfTypeParameter)[0]; } function getInferredTypeParameterConstraint(typeParameter: TypeParameter) { let inferences: Type[] | undefined; if (typeParameter.symbol) { 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) { const typeReference = grandParent; const typeParameters = getTypeParametersForTypeReference(typeReference); if (typeParameters) { const index = typeReference.typeArguments!.indexOf(childTypeParameter); if (index < typeParameters.length) { const declaredConstraint = getConstraintOfTypeParameter(typeParameters[index]); if (declaredConstraint) { // Type parameter constraints can reference other type parameters so // constraints need to be instantiated. If instantiation produces the // type parameter itself, we discard that inference. For example, in // type Foo = [T, U]; // type Bar = T extends Foo ? Foo : T; // the instantiated constraint for U is X, so we discard that inference. const mapper = createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReference, typeParameters)); 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).dotDotDotToken || grandParent.kind === SyntaxKind.RestType || grandParent.kind === SyntaxKind.NamedTupleMember && (grandParent).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); } } } } 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 && type !== errorType) { // Allow errorType to propegate to keep downstream errors suppressed // use keyofConstraintType 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 ? keyofConstraintType : 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) ? getHostSignatureFromJSDoc(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 < length) { const startId = types[i].id; let count = 1; while (i + count < length && types[i + count].id === startId + count) { count++; } if (result.length) { result += ","; } result += startId; if (count > 1) { result += ":" + count; } i += count; } } return result; } // 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 the anyFunctionType. 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 (!(type.flags & excludeKinds)) { result |= getObjectFlags(type); } } return result & ObjectFlags.PropagatingFlags; } 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); target.instantiations.set(id, type); type.objectFlags |= typeArguments ? getPropagatingFlagsOfTypes(typeArguments, /*excludeKinds*/ 0) : 0; type.target = target; type.resolvedTypeArguments = typeArguments; } return type; } function cloneTypeReference(source: TypeReference): TypeReference { const type = createType(source.flags); type.symbol = source.symbol; type.objectFlags = source.objectFlags; type.target = source.target; type.resolvedTypeArguments = source.resolvedTypeArguments; return type; } function createDeferredTypeReference(target: GenericType, node: TypeReferenceNode | ArrayTypeNode | TupleTypeNode, mapper?: TypeMapper): DeferredTypeReference { const aliasSymbol = getAliasSymbolForTypeNode(node); const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); const type = createObjectType(ObjectFlags.Reference, target.symbol); type.target = target; type.node = node; type.mapper = mapper; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = mapper ? instantiateTypes(aliasTypeArguments, mapper) : aliasTypeArguments; return type; } function getTypeArguments(type: TypeReference): readonly Type[] { if (!type.resolvedTypeArguments) { if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedTypeArguments)) { return 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 = 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)); 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 < minTypeArgumentCount || 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, length(node.typeArguments) !== typeParameters.length)) { return createDeferredTypeReference(type, node, /*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, typeArguments); } return checkNoTypeArguments(node, symbol) ? type : errorType; } function getTypeAliasInstantiation(symbol: Symbol, typeArguments: readonly Type[] | undefined): Type { const type = getDeclaredTypeOfSymbol(symbol); if (type === intrinsicMarkerType && intrinsicTypeKinds.has(symbol.escapedName as string) && typeArguments && typeArguments.length === 1) { return getStringMappingType(symbol, typeArguments[0]); } const links = getSymbolLinks(symbol); const typeParameters = links.typeParameters!; const id = getTypeListId(typeArguments); let instantiation = links.instantiations!.get(id); if (!instantiation) { links.instantiations!.set(id, instantiation = instantiateType(type, createTypeMapper(typeParameters, fillMissingTypeArguments(typeArguments, typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(symbol.valueDeclaration))))); } 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 { const type = getDeclaredTypeOfSymbol(symbol); const typeParameters = getSymbolLinks(symbol).typeParameters; if (typeParameters) { const numTypeArguments = length(node.typeArguments); const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); if (numTypeArguments < minTypeArgumentCount || 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; } return getTypeAliasInstantiation(symbol, typeArgumentsFromTypeReferenceNode(node)); } return checkNoTypeArguments(node, symbol) ? type : errorType; } 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 resolveTypeReferenceName(typeReferenceName: EntityNameExpression | EntityName | undefined, meaning: SymbolFlags, ignoreErrors?: boolean) { if (!typeReferenceName) { return unknownSymbol; } return resolveEntityName(typeReferenceName, meaning, ignoreErrors) || unknownSymbol; } 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(getTypeReferenceName(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 getSubstitutionType(baseType: Type, substitute: Type) { if (substitute.flags & TypeFlags.AnyOrUnknown || substitute === baseType) { return baseType; } const id = `${getTypeId(baseType)}>${getTypeId(substitute)}`; const cached = substitutionTypes.get(id); if (cached) { return cached; } const result = createType(TypeFlags.Substitution); result.baseType = baseType; result.substitute = substitute; substitutionTypes.set(id, result); return result; } function isUnaryTupleTypeNode(node: TypeNode) { return node.kind === SyntaxKind.TupleType && (node).elements.length === 1; } function getImpliedConstraint(type: Type, checkNode: TypeNode, extendsNode: TypeNode): Type | undefined { return isUnaryTupleTypeNode(checkNode) && isUnaryTupleTypeNode(extendsNode) ? getImpliedConstraint(type, (checkNode).elements[0], (extendsNode).elements[0]) : getActualTypeVariable(getTypeFromTypeNode(checkNode)) === type ? getTypeFromTypeNode(extendsNode) : undefined; } function getConditionalFlowTypeOfType(type: Type, node: Node) { let constraints: Type[] | undefined; while (node && !isStatement(node) && node.kind !== SyntaxKind.JSDocComment) { const parent = node.parent; if (parent.kind === SyntaxKind.ConditionalType && node === (parent).trueType) { const constraint = getImpliedConstraint(type, (parent).checkType, (parent).extendsType); if (constraint) { constraints = append(constraints, constraint); } } node = parent; } return constraints ? getSubstitutionType(type, getIntersectionType(append(constraints, type))) : 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).typeName ? declarationNameToString((node).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 "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 index = createIndexInfo(target, /*isReadonly*/ false); return createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, indexed === stringType ? index : undefined, indexed === numberType ? index : undefined); } 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(getTypeReferenceName(node), meaning, /*ignoreErrors*/ true); if (symbol === unknownSymbol) { symbol = resolveTypeReferenceName(getTypeReferenceName(node), meaning | SymbolFlags.Value); } else { resolveTypeReferenceName(getTypeReferenceName(node), meaning); // Resolve again to mark errors, if any } type = getTypeReferenceType(node, symbol); } } if (!type) { symbol = resolveTypeReferenceName(getTypeReferenceName(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. links.resolvedType = getRegularTypeOfLiteralType(getWidenedType(checkExpression(node.exprName))); } return links.resolvedType; } function getTypeOfGlobalSymbol(symbol: Symbol | undefined, arity: number): ObjectType { function getTypeDeclaration(symbol: Symbol): Declaration | undefined { const declarations = symbol.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).typeParameters) !== arity) { error(getTypeDeclaration(symbol), Diagnostics.Global_type_0_must_have_1_type_parameter_s, symbolName(symbol), arity); return arity ? emptyGenericType : emptyObjectType; } return type; } 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 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(undefined, name, meaning, diagnostic, name, /*isUse*/ false); } function getGlobalType(name: __String, arity: 0, reportErrors: boolean): ObjectType; function getGlobalType(name: __String, arity: number, reportErrors: boolean): GenericType; function getGlobalType(name: __String, arity: number, reportErrors: boolean): ObjectType | undefined { const symbol = getGlobalTypeSymbol(name, reportErrors); return symbol || reportErrors ? getTypeOfGlobalSymbol(symbol, arity) : undefined; } function getGlobalTypedPropertyDescriptorType() { return deferredGlobalTypedPropertyDescriptorType || (deferredGlobalTypedPropertyDescriptorType = getGlobalType("TypedPropertyDescriptor" as __String, /*arity*/ 1, /*reportErrors*/ true)) || emptyGenericType; } function getGlobalTemplateStringsArrayType() { return deferredGlobalTemplateStringsArrayType || (deferredGlobalTemplateStringsArrayType = getGlobalType("TemplateStringsArray" as __String, /*arity*/ 0, /*reportErrors*/ true)) || emptyObjectType; } function getGlobalImportMetaType() { return deferredGlobalImportMetaType || (deferredGlobalImportMetaType = getGlobalType("ImportMeta" as __String, /*arity*/ 0, /*reportErrors*/ true)) || emptyObjectType; } function getGlobalESSymbolConstructorSymbol(reportErrors: boolean) { return deferredGlobalESSymbolConstructorSymbol || (deferredGlobalESSymbolConstructorSymbol = getGlobalValueSymbol("Symbol" as __String, reportErrors)); } function getGlobalESSymbolType(reportErrors: boolean) { return deferredGlobalESSymbolType || (deferredGlobalESSymbolType = getGlobalType("Symbol" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; } function getGlobalPromiseType(reportErrors: boolean) { return deferredGlobalPromiseType || (deferredGlobalPromiseType = getGlobalType("Promise" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalPromiseLikeType(reportErrors: boolean) { return deferredGlobalPromiseLikeType || (deferredGlobalPromiseLikeType = getGlobalType("PromiseLike" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalPromiseConstructorSymbol(reportErrors: boolean): Symbol | undefined { return deferredGlobalPromiseConstructorSymbol || (deferredGlobalPromiseConstructorSymbol = getGlobalValueSymbol("Promise" as __String, reportErrors)); } function getGlobalPromiseConstructorLikeType(reportErrors: boolean) { return deferredGlobalPromiseConstructorLikeType || (deferredGlobalPromiseConstructorLikeType = getGlobalType("PromiseConstructorLike" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; } function getGlobalAsyncIterableType(reportErrors: boolean) { return deferredGlobalAsyncIterableType || (deferredGlobalAsyncIterableType = getGlobalType("AsyncIterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalAsyncIteratorType(reportErrors: boolean) { return deferredGlobalAsyncIteratorType || (deferredGlobalAsyncIteratorType = getGlobalType("AsyncIterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalAsyncIterableIteratorType(reportErrors: boolean) { return deferredGlobalAsyncIterableIteratorType || (deferredGlobalAsyncIterableIteratorType = getGlobalType("AsyncIterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalAsyncGeneratorType(reportErrors: boolean) { return deferredGlobalAsyncGeneratorType || (deferredGlobalAsyncGeneratorType = getGlobalType("AsyncGenerator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalIterableType(reportErrors: boolean) { return deferredGlobalIterableType || (deferredGlobalIterableType = getGlobalType("Iterable" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalIteratorType(reportErrors: boolean) { return deferredGlobalIteratorType || (deferredGlobalIteratorType = getGlobalType("Iterator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalIterableIteratorType(reportErrors: boolean) { return deferredGlobalIterableIteratorType || (deferredGlobalIterableIteratorType = getGlobalType("IterableIterator" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalGeneratorType(reportErrors: boolean) { return deferredGlobalGeneratorType || (deferredGlobalGeneratorType = getGlobalType("Generator" as __String, /*arity*/ 3, reportErrors)) || emptyGenericType; } function getGlobalIteratorYieldResultType(reportErrors: boolean) { return deferredGlobalIteratorYieldResultType || (deferredGlobalIteratorYieldResultType = getGlobalType("IteratorYieldResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalIteratorReturnResultType(reportErrors: boolean) { return deferredGlobalIteratorReturnResultType || (deferredGlobalIteratorReturnResultType = getGlobalType("IteratorReturnResult" as __String, /*arity*/ 1, reportErrors)) || emptyGenericType; } function getGlobalTypeOrUndefined(name: __String, arity = 0): ObjectType | undefined { const symbol = getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined); return symbol && getTypeOfGlobalSymbol(symbol, arity); } function getGlobalExtractSymbol(): Symbol { return deferredGlobalExtractSymbol || (deferredGlobalExtractSymbol = getGlobalSymbol("Extract" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217 } function getGlobalOmitSymbol(): Symbol { return deferredGlobalOmitSymbol || (deferredGlobalOmitSymbol = getGlobalSymbol("Omit" as __String, SymbolFlags.TypeAlias, Diagnostics.Cannot_find_global_type_0)!); // TODO: GH#18217 } function getGlobalBigIntType(reportErrors: boolean) { return deferredGlobalBigIntType || (deferredGlobalBigIntType = getGlobalType("BigInt" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType; } /** * 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]); } 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); const missingName = some((node as TupleTypeNode).elements, e => e.kind !== SyntaxKind.NamedTupleMember); return getTupleTargetType(elementFlags, readonly, /*associatedNames*/ missingName ? undefined : (node as TupleTypeNode).elements as readonly NamedTupleMember[]); } // 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).typeName, SymbolFlags.Type).flags & SymbolFlags.TypeAlias); case SyntaxKind.TypeQuery: return true; case SyntaxKind.TypeOperator: return (node).operator !== SyntaxKind.UniqueKeyword && mayResolveTypeAlias((node).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).type); case SyntaxKind.RestType: return (node).type.kind !== SyntaxKind.ArrayType || mayResolveTypeAlias(((node).type).elementType); case SyntaxKind.UnionType: case SyntaxKind.IntersectionType: return some((node).types, mayResolveTypeAlias); case SyntaxKind.IndexedAccessType: return mayResolveTypeAlias((node).objectType) || mayResolveTypeAlias((node).indexType); case SyntaxKind.ConditionalType: return mayResolveTypeAlias((node).checkType) || mayResolveTypeAlias((node).extendsType) || mayResolveTypeAlias((node).trueType) || mayResolveTypeAlias((node).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)[]) { 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)[]): 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" : "") + (namedMemberDeclarations && namedMemberDeclarations.length ? "," + map(namedMemberDeclarations, getNodeId).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 = findLastIndex(elementFlags, f => !!(f & (ElementFlags.Required | ElementFlags.Variadic))) + 1; let typeParameters: TypeParameter[] | undefined; const properties: Symbol[] = []; let combinedFlags: ElementFlags = 0; if (arity) { typeParameters = new Array(arity); for (let i = 0; i < arity; i++) { const typeParameter = typeParameters[i] = createTypeParameter(); const flags = elementFlags[i]; combinedFlags |= flags; if (!(combinedFlags & ElementFlags.Variable)) { const property = createSymbol(SymbolFlags.Property | (flags & ElementFlags.Optional ? SymbolFlags.Optional : 0), "" + i as __String, readonly ? CheckFlags.Readonly : 0); property.tupleLabelDeclaration = namedMemberDeclarations?.[i]; property.type = typeParameter; properties.push(property); } } } const fixedLength = properties.length; const lengthSymbol = createSymbol(SymbolFlags.Property, "length" as __String); if (combinedFlags & ElementFlags.Variable) { lengthSymbol.type = numberType; } else { const literalTypes = []; for (let i = minLength; i <= arity; i++) literalTypes.push(getLiteralType(i)); lengthSymbol.type = getUnionType(literalTypes); } properties.push(lengthSymbol); const type = createObjectType(ObjectFlags.Tuple | ObjectFlags.Reference); type.typeParameters = typeParameters; type.outerTypeParameters = undefined; type.localTypeParameters = typeParameters; type.instantiations = new Map(); type.instantiations.set(getTypeListId(type.typeParameters), type); type.target = type; 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.declaredStringIndexInfo = undefined; type.declaredNumberIndexInfo = undefined; 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 && (target).combinedFlags & ElementFlags.Variadic ? createNormalizedTupleType(target as TupleType, typeArguments!) : createTypeReference(target, typeArguments); } function createNormalizedTupleType(target: TupleType, elementTypes: readonly Type[]): Type { // 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; } // If there are no variadic elements with non-generic types, just create a type reference with the same target type. const spreadIndex = findIndex(elementTypes, (t, i) => !!(target.elementFlags[i] & ElementFlags.Variadic) && !(t.flags & TypeFlags.InstantiableNonPrimitive) && !isGenericMappedType(t)); if (spreadIndex < 0) { return createTypeReference(target, elementTypes); } // We have non-generic variadic elements that need normalization. const expandedTypes: Type[] = []; const expandedFlags: ElementFlags[] = []; let expandedDeclarations: (NamedTupleMember | ParameterDeclaration)[] | undefined = []; let optionalIndex = -1; let restTypes: Type[] | undefined; for (let i = 0; i < elementTypes.length; i++) { const type = elementTypes[i]; const flags = target.elementFlags[i]; if (flags & ElementFlags.Variadic) { if (type.flags & TypeFlags.InstantiableNonPrimitive || isGenericMappedType(type)) { // Generic variadic elements stay as they are (except following a rest element). addElementOrRest(type, ElementFlags.Variadic, target.labeledElementDeclarations?.[i]); } else if (isTupleType(type)) { // Spread variadic elements with tuple types into the resulting tuple. forEach(getTypeArguments(type), (t, n) => addElementOrRest(t, type.target.elementFlags[n], type.target.labeledElementDeclarations?.[n])); } else { // Treat everything else as an array type and create a rest element. addElementOrRest(isArrayLikeType(type) && getIndexTypeOfType(type, IndexKind.Number) || errorType, ElementFlags.Rest, target.labeledElementDeclarations?.[i]); } } else { // Copy other element kinds with no change. addElementOrRest(type, flags, target.labeledElementDeclarations?.[i]); } } if (restTypes) { // Create a union of the collected rest element types. expandedTypes[expandedTypes.length - 1] = getUnionType(restTypes); } const tupleTarget = getTupleTargetType(expandedFlags, target.readonly, expandedDeclarations); return tupleTarget === emptyGenericType ? emptyObjectType : expandedFlags.length ? createTypeReference(tupleTarget, expandedTypes) : tupleTarget; function addElementOrRest(type: Type, flags: ElementFlags, declaration: NamedTupleMember | ParameterDeclaration | undefined) { if (restTypes) { // A rest element was previously added, so simply collect the type of this element. restTypes.push(flags & ElementFlags.Variadic ? getIndexedAccessType(type, numberType) : type); } else { if (flags & ElementFlags.Required && optionalIndex >= 0) { // Turn preceding optional elements into required elements for (let i = optionalIndex; i < expandedFlags.length; i++) { if (expandedFlags[i] & ElementFlags.Optional) expandedFlags[i] = ElementFlags.Required; } optionalIndex = -1; } else if (flags & ElementFlags.Optional && optionalIndex < 0) { optionalIndex = expandedFlags.length; } else if (flags & ElementFlags.Rest) { // Start collecting element types when a rest element is added. restTypes = [type]; } expandedTypes.push(type); expandedFlags.push(flags); if (expandedDeclarations && declaration) { expandedDeclarations.push(declaration); } else { expandedDeclarations = undefined; } } } } function sliceTupleType(type: TupleTypeReference, index: number, endSkipCount = 0) { const target = type.target; const endIndex = getTypeReferenceArity(type) - endSkipCount; return index > 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 => getLiteralType("" + i)), getIndexType(type.target.readonly ? globalReadonlyArrayType : globalArrayType))); } function getTypeFromOptionalTypeNode(node: OptionalTypeNode): Type { const type = getTypeFromTypeNode(node.type); return strictNullChecks ? getOptionalType(type) : type; } 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 < 0) { types.splice(~index, 0, type); return true; } return false; } function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) { const flags = type.flags; if (flags & TypeFlags.Union) { return addTypesToUnion(typeSet, includes, (type).types); } // We ignore 'never' types in unions if (!(flags & TypeFlags.Never)) { includes |= flags & TypeFlags.IncludesMask; if (flags & TypeFlags.StructuredOrInstantiable) includes |= TypeFlags.IncludesStructuredOrInstantiable; if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; if (!strictNullChecks && flags & TypeFlags.Nullable) { if (!(getObjectFlags(type) & ObjectFlags.ContainsWideningType)) includes |= TypeFlags.IncludesNonWideningType; } else { const len = typeSet.length; const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues); if (index < 0) { typeSet.splice(~index, 0, type); } } } return includes; } // Add the given types to the given type set. Order is preserved, duplicates are removed, // and nested types of the given kind are flattened into the set. function addTypesToUnion(typeSet: Type[], includes: TypeFlags, types: readonly Type[]): TypeFlags { for (const type of types) { includes = addTypeToUnion(typeSet, includes, type); } return includes; } function isSetOfLiteralsFromSameEnum(types: readonly Type[]): boolean { const first = types[0]; if (first.flags & TypeFlags.EnumLiteral) { const firstEnum = getParentOfSymbol(first.symbol); for (let i = 1; i < types.length; i++) { const other = types[i]; if (!(other.flags & TypeFlags.EnumLiteral) || (firstEnum !== getParentOfSymbol(other.symbol))) { return false; } } return true; } return false; } function removeSubtypes(types: Type[], primitivesOnly: boolean): boolean { const len = types.length; if (len === 0 || isSetOfLiteralsFromSameEnum(types)) { return true; } let i = len; let count = 0; while (i > 0) { i--; const source = types[i]; 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 an upper limit we deem the union type too complex to represent. The // upper limit is 25M for unions of primitives only, and 1M otherwise. This for example // caps union types at 5000 unique literal types and 1000 unique object types. const estimatedCount = (count / (len - i)) * len; if (estimatedCount > (primitivesOnly ? 25000000 : 1000000)) { tracing.instant(tracing.Phase.Check, "removeSubtypes_DepthLimit", { typeIds: types.map(t => t.id) }); error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent); return false; } } count++; if (isTypeRelatedTo(source, target, strictSubtypeRelation) && ( !(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) || !(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) || isTypeDerivedFrom(source, target))) { orderedRemoveItemAt(types, i); break; } } } } return true; } function removeRedundantLiteralTypes(types: Type[], includes: TypeFlags) { let i = types.length; while (i > 0) { i--; const t = types[i]; const remove = t.flags & TypeFlags.StringLiteral && includes & TypeFlags.String || t.flags & TypeFlags.NumberLiteral && includes & TypeFlags.Number || t.flags & TypeFlags.BigIntLiteral && includes & TypeFlags.BigInt || t.flags & TypeFlags.UniqueESSymbol && includes & TypeFlags.ESSymbol || isFreshLiteralType(t) && containsType(types, (t).regularType); if (remove) { orderedRemoveItemAt(types, i); } } } function removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) { const templates = filter(types, isPatternLiteralType); if (templates.length) { let i = types.length; while (i > 0) { i--; const t = types[i]; if (t.flags & TypeFlags.StringLiteral && some(templates, template => isTypeSubtypeOf(t, template))) { orderedRemoveItemAt(types, i); } } } } // 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[]): Type { if (types.length === 0) { return neverType; } if (types.length === 1) { return types[0]; } const typeSet: Type[] = []; const includes = addTypesToUnion(typeSet, 0, types); if (unionReduction !== UnionReduction.None) { if (includes & TypeFlags.AnyOrUnknown) { return includes & TypeFlags.Any ? includes & TypeFlags.IncludesWildcard ? wildcardType : anyType : unknownType; } switch (unionReduction) { case UnionReduction.Literal: if (includes & (TypeFlags.Literal | TypeFlags.UniqueESSymbol)) { removeRedundantLiteralTypes(typeSet, includes); } if (includes & TypeFlags.StringLiteral && includes & TypeFlags.TemplateLiteral) { removeStringLiteralsMatchedByTemplateLiterals(typeSet); } break; case UnionReduction.Subtype: if (!removeSubtypes(typeSet, !(includes & TypeFlags.IncludesStructuredOrInstantiable))) { return errorType; } break; } if (typeSet.length === 0) { return includes & TypeFlags.Null ? includes & TypeFlags.IncludesNonWideningType ? nullType : nullWideningType : includes & TypeFlags.Undefined ? includes & TypeFlags.IncludesNonWideningType ? undefinedType : undefinedWideningType : neverType; } } const objectFlags = (includes & TypeFlags.NotPrimitiveUnion ? 0 : ObjectFlags.PrimitiveUnion) | (includes & TypeFlags.Intersection ? ObjectFlags.ContainsIntersections : 0); return getUnionTypeFromSortedList(typeSet, objectFlags, aliasSymbol, aliasTypeArguments); } function getUnionTypePredicate(signatures: readonly Signature[]): TypePredicate | undefined { let first: TypePredicate | undefined; const types: Type[] = []; for (const sig of signatures) { const pred = getTypePredicateOfSignature(sig); if (!pred || pred.kind === TypePredicateKind.AssertsThis || pred.kind === TypePredicateKind.AssertsIdentifier) { continue; } if (first) { if (!typePredicateKindsMatch(first, pred)) { // No common type predicate. return undefined; } } else { first = pred; } types.push(pred.type); } if (!first) { // No union signatures had a type predicate. return undefined; } const unionType = getUnionType(types); return createTypePredicate(first.kind, first.parameterName, first.parameterIndex, unionType); } 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[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { if (types.length === 0) { return neverType; } if (types.length === 1) { return types[0]; } const id = getTypeListId(types); let type = unionTypes.get(id); if (!type) { type = createType(TypeFlags.Union); unionTypes.set(id, type); type.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); type.types = types; /* Note: This is the alias symbol (or lack thereof) that we see when we first encounter this union type. For aliases of identical unions, eg `type T = A | B; type U = A | B`, the symbol of the first alias encountered is the aliasSymbol. (In the language service, the order may depend on the order in which a user takes actions, such as hovering over symbols.) It's important that we create equivalent union types only once, so that's an unfortunate side effect. */ type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; } 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: ESMap, includes: TypeFlags, type: Type) { const flags = type.flags; if (flags & TypeFlags.Intersection) { return addTypesToIntersection(typeSet, includes, (type).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; } else if ((strictNullChecks || !(flags & TypeFlags.Nullable)) && !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: ESMap, includes: TypeFlags, types: readonly Type[]) { for (const type of types) { includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type)); } return includes; } function removeRedundantPrimitiveTypes(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 || t.flags & TypeFlags.Number && includes & TypeFlags.NumberLiteral || t.flags & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || t.flags & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol; 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. function eachUnionContains(unionTypes: UnionType[], type: Type) { for (const u of unionTypes) { if (!containsType(u.types, type)) { const primitive = type.flags & TypeFlags.StringLiteral ? stringType : type.flags & 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, eg `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)) continue; for (const t2 of literals) { if (isTypeSubtypeOf(t2, t)) { // eg, ``get${T}` & "getX"` is just `"getX"` orderedRemoveItemAt(types, i); break; } else if (isPatternLiteralType(t)) { return true; } } } return false; } function extractIrreducible(types: Type[], flag: TypeFlags) { if (every(types, t => !!(t.flags & TypeFlags.Union) && some((t as UnionType).types, tt => !!(tt.flags & flag)))) { for (let i = 0; i < types.length; i++) { types[i] = filterType(types[i], t => !(t.flags & flag)); } return true; } return false; } // 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 < 0) { return false; } let i = index + 1; // Remove all but the first union of primitive types and collect them in // the unionTypes array. while (i < types.length) { const t = types[i]; if (getObjectFlags(t) & ObjectFlags.PrimitiveUnion) { (unionTypes || (unionTypes = [types[index]])).push(t); orderedRemoveItemAt(types, i); } else { i++; } } // Return false if there was only one union of primitive types if (!unionTypes) { return false; } // We have more than one union of primitive types, now intersect them. For each // type in each union we check if the type is matched in every union and if so // we include it in the result. const checked: Type[] = []; const result: Type[] = []; for (const u of unionTypes) { for (const t of u.types) { if (insertType(checked, t)) { if (eachUnionContains(unionTypes, t)) { insertType(result, t); } } } } // Finally replace the first union with the result types[index] = getUnionTypeFromSortedList(result, ObjectFlags.PrimitiveUnion); return true; } function createIntersectionType(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) { const result = createType(TypeFlags.Intersection); result.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); result.types = types; result.aliasSymbol = aliasSymbol; // See comment in `getUnionTypeFromSortedList`. result.aliasTypeArguments = aliasTypeArguments; return result; } // We normalize combinations of intersection and union types based on the distributive property of the '&' // operator. Specifically, because X & (A | B) is equivalent to X & A | X & B, we can transform intersection // types with union type constituents into equivalent union types with intersection type constituents and // effectively ensure that union types are always at the top level in type representations. // // We do not perform structural deduplication on intersection types. Intersection types are created only by the & // type operator and we can't reduce those because we want to support recursive intersection types. For example, // a type alias of the form "type List = 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[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { const typeMembershipMap: ESMap = new Map(); const includes = addTypesToIntersection(typeMembershipMap, 0, types); const typeSet: Type[] = arrayFrom(typeMembershipMap.values()); // 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 || 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 && includes & TypeFlags.StringLiteral && extractRedundantTemplateLiterals(typeSet)) { return neverType; } if (includes & TypeFlags.Any) { return includes & TypeFlags.IncludesWildcard ? wildcardType : anyType; } if (!strictNullChecks && includes & TypeFlags.Nullable) { return includes & TypeFlags.Undefined ? undefinedType : nullType; } if (includes & TypeFlags.String && includes & TypeFlags.StringLiteral || includes & TypeFlags.Number && includes & TypeFlags.NumberLiteral || includes & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || includes & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol) { removeRedundantPrimitiveTypes(typeSet, includes); } if (includes & TypeFlags.IncludesEmptyObject && includes & TypeFlags.Object) { orderedRemoveItemAt(typeSet, findIndex(typeSet, isEmptyAnonymousObjectType)); } if (typeSet.length === 0) { return unknownType; } if (typeSet.length === 1) { return typeSet[0]; } const id = getTypeListId(typeSet); 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, aliasSymbol, aliasTypeArguments); } else if (extractIrreducible(typeSet, TypeFlags.Undefined)) { result = getUnionType([getIntersectionType(typeSet), undefinedType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } else if (extractIrreducible(typeSet, TypeFlags.Null)) { result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } else { // We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of // the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain. // If the estimated size of the resulting union type exceeds 100000 constituents, report an error. if (!checkCrossProductUnion(typeSet)) { return errorType; } const unionIndex = findIndex(typeSet, t => (t.flags & TypeFlags.Union) !== 0); const unionType = typeSet[unionIndex]; result = getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))), UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } } else { result = createIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); } intersectionTypes.set(id, result); } return result; } function checkCrossProductUnion(types: readonly Type[]) { const size = reduceLeft(types, (n, t) => n * (t.flags & TypeFlags.Union ? (t).types.length : t.flags & TypeFlags.Never ? 0 : 1), 1); if (size >= 100000) { tracing.instant(tracing.Phase.Check, "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 getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { const aliasSymbol = getAliasSymbolForTypeNode(node); links.resolvedType = getIntersectionType(map(node.types, getTypeFromTypeNode), aliasSymbol, getTypeArgumentsForAliasSymbol(aliasSymbol)); } return links.resolvedType; } function createIndexType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { const result = createType(TypeFlags.Index); result.type = type; result.stringsOnly = stringsOnly; return result; } function getIndexTypeForGenericType(type: InstantiableType | UnionOrIntersectionType, stringsOnly: boolean) { return stringsOnly ? type.resolvedStringIndexType || (type.resolvedStringIndexType = createIndexType(type, /*stringsOnly*/ true)) : type.resolvedIndexType || (type.resolvedIndexType = createIndexType(type, /*stringsOnly*/ false)); } function getIndexTypeForMappedType(type: MappedType, noIndexSignatures: boolean | undefined) { const constraint = filterType(getConstraintTypeFromMappedType(type), t => !(noIndexSignatures && t.flags & (TypeFlags.Any | TypeFlags.String))); const nameType = type.declaration.nameType && getTypeFromTypeNode(type.declaration.nameType); return nameType ? mapType(constraint, t => instantiateType(nameType, appendTypeMapping(type.mapper, getTypeParameterFromMappedType(type), t))) : constraint; } // 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. That presumption is // generally true, except when N is a non-distributive conditional type or an instantiable type with non-distributive // conditional type as a constituent. In those cases, we cannot reduce keyof M and need to preserve it as is. function isNonDistributiveNameType(type: Type | undefined): boolean { return !!(type && ( type.flags & TypeFlags.Conditional && !(type).root.isDistributive || type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) && some((type).types, isNonDistributiveNameType) || type.flags & (TypeFlags.Index | TypeFlags.StringMapping) && isNonDistributiveNameType((type).type) || type.flags & TypeFlags.IndexedAccess && isNonDistributiveNameType((type).indexType) || type.flags & TypeFlags.Substitution && isNonDistributiveNameType((type).substitute))); } function getLiteralTypeFromPropertyName(name: PropertyName) { if (isPrivateIdentifier(name)) { return neverType; } return isIdentifier(name) ? getLiteralType(unescapeLeadingUnderscores(name.escapedText)) : getRegularTypeOfLiteralType(isComputedPropertyName(name) ? checkComputedPropertyName(name) : checkExpression(name)); } function getBigIntLiteralType(node: BigIntLiteral): LiteralType { return getLiteralType({ negative: false, base10Value: parsePseudoBigInt(node.text) }); } function getLiteralTypeFromProperty(prop: Symbol, include: TypeFlags) { if (!(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) { let type = getSymbolLinks(getLateBoundSymbol(prop)).nameType; if (!type && !isKnownSymbol(prop)) { if (prop.escapedName === InternalSymbolName.Default) { type = getLiteralType("default"); } else { const name = prop.valueDeclaration && getNameOfDeclaration(prop.valueDeclaration) as PropertyName; type = name && getLiteralTypeFromPropertyName(name) || getLiteralType(symbolName(prop)); } } if (type && type.flags & include) { return type; } } return neverType; } function getLiteralTypeFromProperties(type: Type, include: TypeFlags) { return getUnionType(map(getPropertiesOfType(type), p => getLiteralTypeFromProperty(p, include))); } function getNonEnumNumberIndexInfo(type: Type) { const numberIndexInfo = getIndexInfoOfType(type, IndexKind.Number); return numberIndexInfo !== enumNumberIndexInfo ? numberIndexInfo : undefined; } function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type { type = getReducedType(type); return type.flags & TypeFlags.Union ? getIntersectionType(map((type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : type.flags & TypeFlags.Intersection ? getUnionType(map((type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) : type.flags & TypeFlags.InstantiableNonPrimitive || isGenericTupleType(type) || isGenericMappedType(type) && isNonDistributiveNameType(getNameTypeFromMappedType(type)) ? getIndexTypeForGenericType(type, stringsOnly) : getObjectFlags(type) & ObjectFlags.Mapped ? getIndexTypeForMappedType(type, noIndexSignatures) : type === wildcardType ? wildcardType : type.flags & TypeFlags.Unknown ? neverType : type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType : stringsOnly ? !noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromProperties(type, TypeFlags.StringLiteral) : !noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromProperties(type, TypeFlags.UniqueESSymbol)]) : getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromProperties(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol)]) : getLiteralTypeFromProperties(type, TypeFlags.StringOrNumberLiteralOrUnique); } function getExtractStringType(type: Type) { if (keyofStringsOnly) { return type; } const extractTypeAlias = getGlobalExtractSymbol(); return extractTypeAlias ? getTypeAliasInstantiation(extractTypeAlias, [type, stringType]) : stringType; } function getIndexTypeOrString(type: Type): Type { const indexType = getExtractStringType(getIndexType(type)); return indexType.flags & TypeFlags.Never ? stringType : indexType; } function getTypeFromTypeOperatorNode(node: TypeOperatorNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { switch (node.operator) { case SyntaxKind.KeyOfKeyword: links.resolvedType = getIndexType(getTypeFromTypeNode(node.type)); break; case SyntaxKind.UniqueKeyword: links.resolvedType = node.type.kind === SyntaxKind.SymbolKeyword ? getESSymbolLikeTypeForNode(walkUpParenthesizedTypes(node.parent)) : errorType; break; case SyntaxKind.ReadonlyKeyword: links.resolvedType = getTypeFromTypeNode(node.type); break; default: throw Debug.assertNever(node.operator); } } return links.resolvedType; } function getTypeFromTemplateTypeNode(node: TemplateLiteralTypeNode) { const links = getNodeLinks(node); if (!links.resolvedType) { links.resolvedType = getTemplateLiteralType( [node.head.text, ...map(node.templateSpans, span => span.literal.text)], map(node.templateSpans, span => getTypeFromTypeNode(span.type))); } return links.resolvedType; } function getTemplateLiteralType(texts: readonly string[], types: readonly Type[]): Type { const unionIndex = findIndex(types, t => !!(t.flags & (TypeFlags.Never | TypeFlags.Union))); if (unionIndex >= 0) { return checkCrossProductUnion(types) ? mapType(types[unionIndex], t => getTemplateLiteralType(texts, replaceElement(types, unionIndex, t))) : errorType; } if (contains(types, wildcardType)) { return wildcardType; } const newTypes: Type[] = []; const newTexts: string[] = []; let text = texts[0]; if (!addSpans(texts, types)) { return stringType; } if (newTypes.length === 0) { return getLiteralType(text); } newTexts.push(text); const id = `${getTypeListId(newTypes)}|${map(newTexts, t => t.length).join(",")}|${newTexts.join("")}`; let type = templateLiteralTypes.get(id); if (!type) { templateLiteralTypes.set(id, type = createTemplateLiteralType(newTexts, newTypes)); } return type; function addSpans(texts: readonly string[], types: readonly Type[]): boolean { for (let i = 0; i < types.length; i++) { const t = types[i]; if (t.flags & (TypeFlags.Literal | TypeFlags.Null | TypeFlags.Undefined)) { text += getTemplateStringForType(t) || ""; text += texts[i + 1]; } else if (t.flags & TypeFlags.TemplateLiteral) { text += (t).texts[0]; if (!addSpans((t).texts, (t).types)) return false; text += texts[i + 1]; } else if (isGenericIndexType(t) || isPatternLiteralPlaceholderType(t)) { newTypes.push(t); newTexts.push(text); text = texts[i + 1]; } else { return false; } } return true; } } function getTemplateStringForType(type: Type) { return type.flags & TypeFlags.StringLiteral ? (type).value : type.flags & TypeFlags.NumberLiteral ? "" + (type).value : type.flags & TypeFlags.BigIntLiteral ? pseudoBigIntToString((type).value) : type.flags & TypeFlags.BooleanLiteral ? (type).intrinsicName : type.flags & TypeFlags.Null ? "null" : type.flags & TypeFlags.Undefined ? "undefined" : undefined; } function createTemplateLiteralType(texts: readonly string[], types: readonly Type[]) { const type = createType(TypeFlags.TemplateLiteral); type.texts = texts; type.types = types; return type; } function getStringMappingType(symbol: Symbol, type: Type): Type { return type.flags & (TypeFlags.Union | TypeFlags.Never) ? mapType(type, t => getStringMappingType(symbol, t)) : isGenericIndexType(type) ? getStringMappingTypeForGenericType(symbol, type) : type.flags & TypeFlags.StringLiteral ? getLiteralType(applyStringMapping(symbol, (type).value)) : type; } function applyStringMapping(symbol: Symbol, str: string) { switch (intrinsicTypeKinds.get(symbol.escapedName as string)) { case IntrinsicTypeKind.Uppercase: return str.toUpperCase(); case IntrinsicTypeKind.Lowercase: return str.toLowerCase(); case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1); case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1); } return str; } function getStringMappingTypeForGenericType(symbol: Symbol, type: Type): Type { const id = `${getSymbolId(symbol)},${getTypeId(type)}`; let result = stringMappingTypes.get(id); if (!result) { stringMappingTypes.set(id, result = createStringMappingType(symbol, type)); } return result; } function createStringMappingType(symbol: Symbol, type: Type) { const result = createType(TypeFlags.StringMapping); result.symbol = symbol; result.type = type; return result; } function createIndexedAccessType(objectType: Type, indexType: Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, shouldIncludeUndefined: boolean) { const type = createType(TypeFlags.IndexedAccess); type.objectType = objectType; type.indexType = indexType; type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = aliasTypeArguments; type.noUncheckedIndexedAccessCandidate = shouldIncludeUndefined; 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) { return isJSLiteralType(getResolvedBaseConstraint(type)); } return false; } function getPropertyNameFromIndex(indexType: Type, accessNode: StringLiteral | Identifier | PrivateIdentifier | ObjectBindingPattern | ArrayBindingPattern | ComputedPropertyName | NumericLiteral | IndexedAccessTypeNode | ElementAccessExpression | SyntheticExpression | undefined) { const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; return isTypeUsableAsPropertyName(indexType) ? getPropertyNameFromType(indexType) : accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false) ? getPropertyNameForKnownSymbolName(idText((accessExpression.argumentExpression).name)) : 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) { return !(symbol.flags & (SymbolFlags.Function | SymbolFlags.Method)) || !isCallLikeExpression(findAncestor(node, n => !isAccessExpression(n)) || node.parent) && every(symbol.declarations, d => !isFunctionLike(d) || !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated)); } function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, suppressNoImplicitAnyError: boolean, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags, noUncheckedIndexedAccessCandidate?: boolean, reportDeprecated?: boolean) { const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; const propName = accessNode && isPrivateIdentifier(accessNode) ? undefined : getPropertyNameFromIndex(indexType, accessNode); if (propName !== undefined) { const prop = getPropertyOfType(objectType, propName); if (prop) { if (reportDeprecated && accessNode && getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(accessNode, prop)) { const deprecatedNode = accessExpression?.argumentExpression ?? (isIndexedAccessTypeNode(accessNode) ? accessNode.indexType : accessNode); errorOrSuggestion(/* isError */ false, deprecatedNode, Diagnostics._0_is_deprecated, propName as string); } if (accessExpression) { markPropertyAsReferenced(prop, accessExpression, /*isThisAccess*/ accessExpression.expression.kind === SyntaxKind.ThisKeyword); if (isAssignmentToReadonlyEntity(accessExpression, prop, getAssignmentTargetKind(accessExpression))) { error(accessExpression.argumentExpression, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(prop)); return undefined; } if (accessFlags & AccessFlags.CacheSymbol) { getNodeLinks(accessNode!).resolvedSymbol = prop; } if (isThisPropertyAccessInConstructor(accessExpression, prop)) { return autoType; } } const propType = getTypeOfSymbol(prop); return accessExpression && getAssignmentTargetKind(accessExpression) !== AssignmentKind.Definite ? getFlowTypeOfReference(accessExpression, propType) : propType; } if (everyType(objectType, isTupleType) && isNumericLiteralName(propName) && +propName >= 0) { if (accessNode && everyType(objectType, t => !(t).target.hasRestElement) && !(accessFlags & AccessFlags.NoTupleBoundsCheck)) { const indexNode = getIndexNodeForAccessExpression(accessNode); if (isTupleType(objectType)) { error(indexNode, Diagnostics.Tuple_type_0_of_length_1_has_no_element_at_index_2, typeToString(objectType), getTypeReferenceArity(objectType), unescapeLeadingUnderscores(propName)); } else { error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); } } errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, IndexKind.Number)); return mapType(objectType, t => { const restType = getRestTypeOfTupleType(t) || undefinedType; return noUncheckedIndexedAccessCandidate ? getUnionType([restType, undefinedType]) : restType; }); } } if (!(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike)) { if (objectType.flags & (TypeFlags.Any | TypeFlags.Never)) { return objectType; } const stringIndexInfo = getIndexInfoOfType(objectType, IndexKind.String); const indexInfo = isTypeAssignableToKind(indexType, TypeFlags.NumberLike) && getIndexInfoOfType(objectType, IndexKind.Number) || stringIndexInfo; if (indexInfo) { if (accessFlags & AccessFlags.NoIndexSignatures && indexInfo === stringIndexInfo) { if (accessExpression) { error(accessExpression, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(originalObjectType)); } return undefined; } if (accessNode && !isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { const indexNode = getIndexNodeForAccessExpression(accessNode); error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); return noUncheckedIndexedAccessCandidate ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; } errorIfWritingToReadonlyIndex(indexInfo); return noUncheckedIndexedAccessCandidate ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; } if (indexType.flags & TypeFlags.Never) { return neverType; } if (isJSLiteralType(objectType)) { return anyType; } if (accessExpression && !isConstEnumObjectType(objectType)) { if (objectType.symbol === globalThisSymbol && propName !== undefined && globalThisSymbol.exports!.has(propName) && (globalThisSymbol.exports!.get(propName)!.flags & SymbolFlags.BlockScoped)) { error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); } else if (noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors && !suppressNoImplicitAnyError) { if (propName !== undefined && typeHasStaticProperty(propName, objectType)) { error(accessExpression, Diagnostics.Property_0_is_a_static_member_of_type_1, propName as string, typeToString(objectType)); } else if (getIndexTypeOfType(objectType, IndexKind.Number)) { error(accessExpression.argumentExpression, Diagnostics.Element_implicitly_has_an_any_type_because_index_expression_is_not_of_type_number); } else { let suggestion: string | undefined; if (propName !== undefined && (suggestion = getSuggestionForNonexistentProperty(propName as string, objectType))) { if (suggestion !== undefined) { error(accessExpression.argumentExpression, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName as string, typeToString(objectType), suggestion); } } else { const suggestion = getSuggestionForNonexistentIndexSignature(objectType, accessExpression, indexType); if (suggestion !== undefined) { error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature_Did_you_mean_to_call_1, typeToString(objectType), suggestion); } else { let errorInfo: DiagnosticMessageChain | undefined; if (indexType.flags & TypeFlags.EnumLiteral) { errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + typeToString(indexType) + "]", typeToString(objectType)); } else if (indexType.flags & TypeFlags.UniqueESSymbol) { const symbolName = getFullyQualifiedName((indexType as UniqueESSymbolType).symbol, accessExpression); errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + symbolName + "]", typeToString(objectType)); } else if (indexType.flags & TypeFlags.StringLiteral) { errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType)); } else if (indexType.flags & TypeFlags.NumberLiteral) { errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as NumberLiteralType).value, typeToString(objectType)); } else if (indexType.flags & (TypeFlags.Number | TypeFlags.String)) { errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.No_index_signature_with_a_parameter_of_type_0_was_found_on_type_1, typeToString(indexType), typeToString(objectType)); } errorInfo = chainDiagnosticMessages( errorInfo, Diagnostics.Element_implicitly_has_an_any_type_because_expression_of_type_0_can_t_be_used_to_index_type_1, typeToString(fullIndexType), typeToString(objectType) ); diagnostics.add(createDiagnosticForNodeFromMessageChain(accessExpression, errorInfo)); } } } } return undefined; } } if (isJSLiteralType(objectType)) { return anyType; } if (accessNode) { const indexNode = getIndexNodeForAccessExpression(accessNode); if (indexType.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral)) { error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, "" + (indexType).value, typeToString(objectType)); } else if (indexType.flags & (TypeFlags.String | TypeFlags.Number)) { error(indexNode, Diagnostics.Type_0_has_no_matching_index_signature_for_type_1, typeToString(objectType), typeToString(indexType)); } else { error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); } } if (isTypeAny(indexType)) { return indexType; } return undefined; function errorIfWritingToReadonlyIndex(indexInfo: IndexInfo | undefined): void { if (indexInfo && indexInfo.isReadonly && accessExpression && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression))) { error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); } } } function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression) { return accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode.argumentExpression : accessNode.kind === SyntaxKind.IndexedAccessType ? accessNode.indexType : accessNode.kind === SyntaxKind.ComputedPropertyName ? accessNode.expression : accessNode; } function isPatternLiteralPlaceholderType(type: Type) { return templateConstraintType.types.indexOf(type) !== -1 || !!(type.flags & TypeFlags.Any); } function isPatternLiteralType(type: Type) { return !!(type.flags & TypeFlags.TemplateLiteral) && every((type as TemplateLiteralType).types, isPatternLiteralPlaceholderType); } function isGenericObjectType(type: Type): boolean { if (type.flags & TypeFlags.UnionOrIntersection) { if (!((type).objectFlags & ObjectFlags.IsGenericObjectTypeComputed)) { (type).objectFlags |= ObjectFlags.IsGenericObjectTypeComputed | (some((type).types, isGenericObjectType) ? ObjectFlags.IsGenericObjectType : 0); } return !!((type).objectFlags & ObjectFlags.IsGenericObjectType); } return !!(type.flags & TypeFlags.InstantiableNonPrimitive) || isGenericMappedType(type) || isGenericTupleType(type); } function isGenericIndexType(type: Type): boolean { if (type.flags & TypeFlags.UnionOrIntersection) { if (!((type).objectFlags & ObjectFlags.IsGenericIndexTypeComputed)) { (type).objectFlags |= ObjectFlags.IsGenericIndexTypeComputed | (some((type).types, isGenericIndexType) ? ObjectFlags.IsGenericIndexType : 0); } return !!((type).objectFlags & ObjectFlags.IsGenericIndexType); } return !!(type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) && !isPatternLiteralType(type); } function isThisTypeParameter(type: Type): boolean { return !!(type.flags & TypeFlags.TypeParameter && (type).isThisType); } function getSimplifiedType(type: Type, writing: boolean): Type { return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(type, writing) : type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(type, writing) : type; } function distributeIndexOverObjectType(objectType: Type, indexType: Type, writing: boolean) { // (T | U)[K] -> T[K] | U[K] (reading) // (T | U)[K] -> T[K] & U[K] (writing) // (T & U)[K] -> T[K] & U[K] if (objectType.flags & TypeFlags.UnionOrIntersection) { const types = map((objectType as UnionOrIntersectionType).types, t => getSimplifiedType(getIndexedAccessType(t, indexType), writing)); return objectType.flags & TypeFlags.Intersection || writing ? getIntersectionType(types) : getUnionType(types); } } function distributeObjectOverIndexType(objectType: Type, indexType: Type, writing: boolean) { // T[A | B] -> T[A] | T[B] (reading) // T[A | B] -> T[A] & T[B] (writing) if (indexType.flags & TypeFlags.Union) { const types = map((indexType as UnionType).types, t => getSimplifiedType(getIndexedAccessType(objectType, t), writing)); return writing ? getIntersectionType(types) : getUnionType(types); } } function unwrapSubstitution(type: Type): Type { if (type.flags & TypeFlags.Substitution) { return (type as SubstitutionType).substitute; } return type; } // 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 = unwrapSubstitution(getSimplifiedType(type.objectType, writing)); const indexType = getSimplifiedType(type.indexType, writing); // T[A | B] -> T[A] | T[B] (reading) // T[A | B] -> T[A] & T[B] (writing) const distributedOverIndex = distributeObjectOverIndexType(objectType, indexType, writing); if (distributedOverIndex) { return type[cache] = distributedOverIndex; } // Only do the inner distributions if the index can no longer be instantiated to cause index distribution again if (!(indexType.flags & TypeFlags.Instantiable)) { // (T | U)[K] -> T[K] | U[K] (reading) // (T | U)[K] -> T[K] & U[K] (writing) // (T & U)[K] -> T[K] & U[K] const distributedOverObject = distributeIndexOverObjectType(objectType, indexType, writing); if (distributedOverObject) { return type[cache] = distributedOverObject; } } // So ultimately (reading): // ((A & B) | C)[K1 | K2] -> ((A & B) | C)[K1] | ((A & B) | C)[K2] -> (A & B)[K1] | C[K1] | (A & B)[K2] | C[K2] -> (A[K1] & B[K1]) | C[K1] | (A[K2] & B[K2]) | C[K2] // A generic tuple type indexed by a number exists only when the index type doesn't select a // fixed element. We simplify to either the combined type of all elements (when the index type // the actual number type) or to the combined type of all non-fixed elements. if (isGenericTupleType(objectType) && indexType.flags & TypeFlags.NumberLike) { const elementType = getElementTypeOfSliceOfTupleType(objectType, indexType.flags & TypeFlags.Number ? 0 : objectType.target.fixedLength, /*endSkipCount*/ 0, writing); if (elementType) { return type[cache] = elementType; } } // If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper // that substitutes the index type for P. For example, for an index access { [P in K]: Box }[X], we // construct the type Box. if (isGenericMappedType(objectType)) { return type[cache] = mapType(substituteIndexedMappedType(objectType, type.indexType), t => getSimplifiedType(t, writing)); } return type[cache] = type; } function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) { const checkType = type.checkType; const extendsType = type.extendsType; const trueType = getTrueTypeFromConditionalType(type); const falseType = getFalseTypeFromConditionalType(type); // Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`. if (falseType.flags & TypeFlags.Never && getActualTypeVariable(trueType) === getActualTypeVariable(checkType)) { if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true return getSimplifiedType(trueType, writing); } else if (isIntersectionEmpty(checkType, extendsType)) { // Always false return neverType; } } else if (trueType.flags & TypeFlags.Never && getActualTypeVariable(falseType) === getActualTypeVariable(checkType)) { if (!(checkType.flags & TypeFlags.Any) && isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true return neverType; } else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false return getSimplifiedType(falseType, writing); } } return type; } /** * Invokes union simplification logic to determine if an intersection is considered empty as a union constituent */ function isIntersectionEmpty(type1: Type, type2: Type) { return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never); } function substituteIndexedMappedType(objectType: MappedType, index: Type) { const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]); const templateMapper = combineTypeMappers(objectType.mapper, mapper); return instantiateType(getTemplateTypeFromMappedType(objectType), templateMapper); } function getIndexedAccessType(objectType: Type, indexType: Type, noUncheckedIndexedAccessCandidate?: boolean, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], accessFlags = AccessFlags.None): Type { return getIndexedAccessTypeOrUndefined(objectType, indexType, noUncheckedIndexedAccessCandidate, accessNode, accessFlags, aliasSymbol, aliasTypeArguments) || (accessNode ? errorType : unknownType); } function indexTypeLessThan(indexType: Type, limit: number) { return everyType(indexType, t => { if (t.flags & TypeFlags.StringOrNumberLiteral) { const propName = getPropertyNameFromType(t); if (isNumericLiteralName(propName)) { const index = +propName; return index >= 0 && index < limit; } } return false; }); } function getIndexedAccessTypeOrUndefined(objectType: Type, indexType: Type, noUncheckedIndexedAccessCandidate?: boolean, accessNode?: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression, accessFlags = AccessFlags.None, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type | undefined { if (objectType === wildcardType || indexType === wildcardType) { return wildcardType; } const shouldIncludeUndefined = noUncheckedIndexedAccessCandidate || (!!compilerOptions.noUncheckedIndexedAccess && (accessFlags & (AccessFlags.Writing | AccessFlags.ExpressionPosition)) === AccessFlags.ExpressionPosition); // If the object type has a string index signature and no other members we know that the result will // always be the type of that index signature and we can simplify accordingly. if (isStringIndexSignatureOnlyType(objectType) && !(indexType.flags & TypeFlags.Nullable) && isTypeAssignableToKind(indexType, TypeFlags.String | TypeFlags.Number)) { indexType = stringType; } // If the index type is generic, or if the object type is generic and doesn't originate in an expression and // the operation isn't exclusively indexing the fixed (non-variadic) portion of a tuple type, we are performing // a higher-order index access where we cannot meaningfully access the properties of the object type. Note that // for a generic T and a non-generic K, we eagerly resolve T[K] if it originates in an expression. This is to // preserve backwards compatibility. For example, an element access 'this["foo"]' has always been resolved // eagerly using the constraint type of 'this' at the given location. if (isGenericIndexType(indexType) || (accessNode && accessNode.kind !== SyntaxKind.IndexedAccessType ? isGenericTupleType(objectType) && !indexTypeLessThan(indexType, objectType.target.fixedLength) : isGenericObjectType(objectType) && !(isTupleType(objectType) && indexTypeLessThan(indexType, objectType.target.fixedLength)))) { if (objectType.flags & TypeFlags.AnyOrUnknown) { return objectType; } // Defer the operation by creating an indexed access type. const id = objectType.id + "," + indexType.id + (shouldIncludeUndefined ? "?" : ""); let type = indexedAccessTypes.get(id); if (!type) { indexedAccessTypes.set(id, type = createIndexedAccessType(objectType, indexType, aliasSymbol, aliasTypeArguments, shouldIncludeUndefined)); } return type; } // In the following we resolve T[K] to the type of the property in T selected by K. // We treat boolean as different from other unions to improve errors; // skipping straight to getPropertyTypeForIndexType gives errors with 'boolean' instead of 'true'. const apparentObjectType = getReducedApparentType(objectType); if (indexType.flags & TypeFlags.Union && !(indexType.flags & TypeFlags.Boolean)) { const propTypes: Type[] = []; let wasMissingProp = false; for (const t of (indexType).types) { const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, wasMissingProp, accessNode, accessFlags, shouldIncludeUndefined); if (propType) { propTypes.push(propType); } else if (!accessNode) { // If there's no error node, we can immeditely stop, since error reporting is off return undefined; } else { // Otherwise we set a flag and return at the end of the loop so we still mark all errors wasMissingProp = true; } } if (wasMissingProp) { return undefined; } return accessFlags & AccessFlags.Writing ? getIntersectionType(propTypes, aliasSymbol, aliasTypeArguments) : getUnionType(propTypes, UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, /* supressNoImplicitAnyError */ false, accessNode, accessFlags | AccessFlags.CacheSymbol, shouldIncludeUndefined, /* reportDeprecated */ true); } function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) { const links = getNodeLinks(node); if (!links.resolvedType) { const objectType = getTypeFromTypeNode(node.objectType); const indexType = getTypeFromTypeNode(node.indexType); const potentialAlias = getAliasSymbolForTypeNode(node); const resolved = getIndexedAccessType(objectType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, node, potentialAlias, getTypeArgumentsForAliasSymbol(potentialAlias)); links.resolvedType = resolved.flags & TypeFlags.IndexedAccess && (resolved).objectType === objectType && (resolved).indexType === indexType ? getConditionalFlowTypeOfType(resolved, node) : resolved; } return links.resolvedType; } function getTypeFromMappedTypeNode(node: MappedTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { const type = createObjectType(ObjectFlags.Mapped, node.symbol); type.declaration = node; type.aliasSymbol = getAliasSymbolForTypeNode(node); type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(type.aliasSymbol); links.resolvedType = type; // Eagerly resolve the constraint type which forces an error if the constraint type circularly // references itself through one or more type aliases. getConstraintTypeFromMappedType(type); } return links.resolvedType; } function getActualTypeVariable(type: Type): Type { if (type.flags & TypeFlags.Substitution) { return (type).baseType; } if (type.flags & TypeFlags.IndexedAccess && ( (type).objectType.flags & TypeFlags.Substitution || (type).indexType.flags & TypeFlags.Substitution)) { return getIndexedAccessType(getActualTypeVariable((type).objectType), getActualTypeVariable((type).indexType)); } return type; } function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined): Type { let result; let extraTypes: Type[] | undefined; // 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. This means such types aren't subject to the instatiation depth limiter. while (true) { const checkType = instantiateType(root.checkType, mapper); const checkTypeInstantiable = isGenericObjectType(checkType) || isGenericIndexType(checkType); const extendsType = instantiateType(root.extendsType, mapper); if (checkType === wildcardType || extendsType === wildcardType) { return wildcardType; } let combinedMapper: TypeMapper | undefined; if (root.inferTypeParameters) { const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None); // We skip inference of the possible `infer` types unles the `extendsType` _is_ an infer type // if it was, it's trivial to say that extendsType = checkType, however such a pattern is used to // "reset" the type being build up during constraint calculation and avoid making an apparently "infinite" constraint // so in those cases we refain from performing inference and retain the uninfered type parameter if (!checkTypeInstantiable || !some(root.inferTypeParameters, t => t === extendsType)) { // 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); } combinedMapper = mergeTypeMappers(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 (!checkTypeInstantiable && !isGenericObjectType(inferredExtendsType) && !isGenericIndexType(inferredExtendsType)) { // Return falseType for a definitely false extends check. We check an instantiations of the two // types with type parameters mapped to the wildcard type, the most permissive instantiations // possible (the wildcard type is assignable to and from all types). If those are not related, // then no instantiations will be and we can just return the false branch type. if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && (checkType.flags & TypeFlags.Any || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { // Return union of trueType and falseType for 'any' since it matches anything if (checkType.flags & TypeFlags.Any) { (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).root; if (newRoot.node.parent === root.node && (!newRoot.isDistributive || newRoot.checkType === root.checkType)) { root = newRoot; 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))) { result = instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper); break; } } // Return a deferred type for a check that is neither definitely true nor definitely false const erasedCheckType = getActualTypeVariable(checkType); result = createType(TypeFlags.Conditional); result.root = root; result.checkType = erasedCheckType; result.extendsType = extendsType; result.mapper = mapper; result.combinedMapper = combinedMapper; result.aliasSymbol = root.aliasSymbol; result.aliasTypeArguments = instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217 break; } return extraTypes ? getUnionType(append(extraTypes, result)) : result; } function getTrueTypeFromConditionalType(type: ConditionalType) { return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.mapper)); } function getFalseTypeFromConditionalType(type: ConditionalType) { return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(getTypeFromTypeNode(type.root.node.falseType), type.mapper)); } function getInferredTrueTypeFromConditionalType(type: ConditionalType) { return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = type.combinedMapper ? instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.combinedMapper) : getTrueTypeFromConditionalType(type)); } function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] | undefined { let result: TypeParameter[] | undefined; if (node.locals) { node.locals.forEach(symbol => { if (symbol.flags & SymbolFlags.TypeParameter) { result = append(result, getDeclaredTypeOfSymbol(symbol)); } }); } return result; } function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { const checkType = getTypeFromTypeNode(node.checkType); const aliasSymbol = getAliasSymbolForTypeNode(node); const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true); const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, node)); const root: ConditionalRoot = { node, checkType, extendsType: getTypeFromTypeNode(node.extendsType), isDistributive: !!(checkType.flags & TypeFlags.TypeParameter), inferTypeParameters: getInferTypeParameters(node), outerTypeParameters, instantiations: undefined, aliasSymbol, aliasTypeArguments }; links.resolvedType = getConditionalType(root, /*mapper*/ undefined); if (outerTypeParameters) { root.instantiations = new Map(); root.instantiations.set(getTypeListId(outerTypeParameters), links.resolvedType); } } return links.resolvedType; } function getTypeFromInferTypeNode(node: InferTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { links.resolvedType = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node.typeParameter)); } return links.resolvedType; } function getIdentifierChain(node: EntityName): Identifier[] { if (isIdentifier(node)) { return [node]; } else { return append(getIdentifierChain(node.left), node.right); } } function getTypeFromImportTypeNode(node: ImportTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { if (node.isTypeOf && node.typeArguments) { // Only the non-typeof form can make use of type arguments error(node, Diagnostics.Type_arguments_cannot_be_used_here); links.resolvedSymbol = unknownSymbol; return links.resolvedType = errorType; } if (!isLiteralImportTypeNode(node)) { error(node.argument, Diagnostics.String_literal_expected); links.resolvedSymbol = unknownSymbol; return links.resolvedType = errorType; } const targetMeaning = node.isTypeOf ? SymbolFlags.Value : node.flags & NodeFlags.JSDoc ? SymbolFlags.Value | SymbolFlags.Type : SymbolFlags.Type; // TODO: Future work: support unions/generics/whatever via a deferred import-type const innerModuleSymbol = resolveExternalModuleName(node, node.argument.literal); if (!innerModuleSymbol) { links.resolvedSymbol = unknownSymbol; return links.resolvedType = errorType; } const moduleSymbol = resolveExternalModuleSymbol(innerModuleSymbol, /*dontResolveAlias*/ false); if (!nodeIsMissing(node.qualifier)) { const nameStack: Identifier[] = getIdentifierChain(node.qualifier!); let currentNamespace = moduleSymbol; let current: Identifier | undefined; while (current = nameStack.shift()) { const meaning = nameStack.length ? SymbolFlags.Namespace : targetMeaning; // typeof a.b.c is normally resolved using `checkExpression` which in turn defers to `checkQualifiedName` // That, in turn, ultimately uses `getPropertyOfType` on the type of the symbol, which differs slightly from // the `exports` lookup process that only looks up namespace members which is used for most type references const mergedResolvedSymbol = getMergedSymbol(resolveSymbol(currentNamespace)); const next = node.isTypeOf ? getPropertyOfType(getTypeOfSymbol(mergedResolvedSymbol), current.escapedText) : getSymbol(getExportsOfSymbol(mergedResolvedSymbol), current.escapedText, meaning); if (!next) { error(current, Diagnostics.Namespace_0_has_no_exported_member_1, getFullyQualifiedName(currentNamespace), declarationNameToString(current)); return links.resolvedType = errorType; } getNodeLinks(current).resolvedSymbol = next; getNodeLinks(current.parent).resolvedSymbol = next; currentNamespace = next; } links.resolvedType = resolveImportSymbolType(node, links, currentNamespace, targetMeaning); } else { if (moduleSymbol.flags & targetMeaning) { links.resolvedType = resolveImportSymbolType(node, links, moduleSymbol, targetMeaning); } else { const errorMessage = targetMeaning === SymbolFlags.Value ? Diagnostics.Module_0_does_not_refer_to_a_value_but_is_used_as_a_value_here : Diagnostics.Module_0_does_not_refer_to_a_type_but_is_used_as_a_type_here_Did_you_mean_typeof_import_0; error(node, errorMessage, node.argument.literal.text); links.resolvedSymbol = unknownSymbol; links.resolvedType = errorType; } } } return links.resolvedType; } function resolveImportSymbolType(node: ImportTypeNode, links: NodeLinks, symbol: Symbol, meaning: SymbolFlags) { const resolvedSymbol = resolveSymbol(symbol); links.resolvedSymbol = resolvedSymbol; if (meaning === SymbolFlags.Value) { return getTypeOfSymbol(symbol); // intentionally doesn't use resolved symbol so type is cached as expected on the alias } else { return getTypeReferenceType(node, resolvedSymbol); // getTypeReferenceType doesn't handle aliases - it must get the resolved symbol } } function getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node: TypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { // Deferred resolution of members is handled by resolveObjectTypeMembers const aliasSymbol = getAliasSymbolForTypeNode(node); if (getMembersOfSymbol(node.symbol).size === 0 && !aliasSymbol) { links.resolvedType = emptyTypeLiteralType; } else { let type = createObjectType(ObjectFlags.Anonymous, node.symbol); type.aliasSymbol = aliasSymbol; type.aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); if (isJSDocTypeLiteral(node) && node.isArrayType) { type = createArrayType(type); } links.resolvedType = type; } } return links.resolvedType; } function getAliasSymbolForTypeNode(node: Node) { let host = node.parent; while (isParenthesizedTypeNode(host) || isJSDocTypeExpression(host) || isTypeOperatorNode(host) && host.operator === SyntaxKind.ReadonlyKeyword) { host = host.parent; } return isTypeAlias(host) ? getSymbolOfNode(host) : undefined; } function getTypeArgumentsForAliasSymbol(symbol: Symbol | undefined) { return symbol ? getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol) : undefined; } function isNonGenericObjectType(type: Type) { return !!(type.flags & TypeFlags.Object) && !isGenericMappedType(type); } function isEmptyObjectTypeOrSpreadsIntoEmptyObject(type: Type) { return isEmptyObjectType(type) || !!(type.flags & (TypeFlags.Null | TypeFlags.Undefined | TypeFlags.BooleanLike | TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.EnumLike | TypeFlags.NonPrimitive | TypeFlags.Index)); } function tryMergeUnionOfObjectTypeAndEmptyObject(type: UnionType, readonly: boolean): Type | undefined { if (type.types.length === 2) { const firstType = type.types[0]; const secondType = type.types[1]; if (every(type.types, isEmptyObjectTypeOrSpreadsIntoEmptyObject)) { return isEmptyObjectType(firstType) ? firstType : isEmptyObjectType(secondType) ? secondType : emptyObjectType; } if (isEmptyObjectTypeOrSpreadsIntoEmptyObject(firstType)) { return getAnonymousPartialType(secondType); } if (isEmptyObjectTypeOrSpreadsIntoEmptyObject(secondType)) { 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, readonly ? CheckFlags.Readonly : 0); result.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop); result.declarations = prop.declarations; result.nameType = getSymbolLinks(prop).nameType; result.syntheticOrigin = prop; members.set(prop.escapedName, result); } } const spread = createAnonymousType( type.symbol, members, emptyArray, emptyArray, getIndexInfoOfType(type, IndexKind.String), getIndexInfoOfType(type, IndexKind.Number)); 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; } if (left.flags & TypeFlags.Union) { const merged = tryMergeUnionOfObjectTypeAndEmptyObject(left as UnionType, readonly); if (merged) { return getSpreadType(merged, right, symbol, objectFlags, readonly); } return checkCrossProductUnion([left, right]) ? mapType(left, t => getSpreadType(t, right, symbol, objectFlags, readonly)) : errorType; } if (right.flags & TypeFlags.Union) { const merged = tryMergeUnionOfObjectTypeAndEmptyObject(right as UnionType, readonly); if (merged) { return getSpreadType(left, merged, symbol, objectFlags, readonly); } 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).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>(); let stringIndexInfo: IndexInfo | undefined; let numberIndexInfo: IndexInfo | undefined; if (left === emptyObjectType) { // for the first spread element, left === emptyObjectType, so take the right's string indexer stringIndexInfo = getIndexInfoOfType(right, IndexKind.String); numberIndexInfo = getIndexInfoOfType(right, IndexKind.Number); } else { stringIndexInfo = unionSpreadIndexInfos(getIndexInfoOfType(left, IndexKind.String), getIndexInfoOfType(right, IndexKind.String)); numberIndexInfo = unionSpreadIndexInfos(getIndexInfoOfType(left, IndexKind.Number), getIndexInfoOfType(right, IndexKind.Number)); } for (const rightProp of getPropertiesOfType(right)) { if (getDeclarationModifierFlagsFromSymbol(rightProp) & (ModifierFlags.Private | ModifierFlags.Protected)) { skippedPrivateMembers.add(rightProp.escapedName); } else if (isSpreadableProperty(rightProp)) { members.set(rightProp.escapedName, getSpreadSymbol(rightProp, readonly)); } } for (const leftProp of getPropertiesOfType(left)) { if (skippedPrivateMembers.has(leftProp.escapedName) || !isSpreadableProperty(leftProp)) { continue; } if (members.has(leftProp.escapedName)) { const rightProp = members.get(leftProp.escapedName)!; const rightType = getTypeOfSymbol(rightProp); if (rightProp.flags & SymbolFlags.Optional) { const declarations = concatenate(leftProp.declarations, rightProp.declarations); const flags = SymbolFlags.Property | (leftProp.flags & SymbolFlags.Optional); const result = createSymbol(flags, leftProp.escapedName); result.type = getUnionType([getTypeOfSymbol(leftProp), getTypeWithFacts(rightType, TypeFacts.NEUndefined)]); result.leftSpread = leftProp; result.rightSpread = rightProp; result.declarations = declarations; result.nameType = getSymbolLinks(leftProp).nameType; members.set(leftProp.escapedName, result); } } else { members.set(leftProp.escapedName, getSpreadSymbol(leftProp, readonly)); } } const spread = createAnonymousType( symbol, members, emptyArray, emptyArray, getIndexInfoWithReadonly(stringIndexInfo, readonly), getIndexInfoWithReadonly(numberIndexInfo, 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, isPrivateIdentifierPropertyDeclaration) && (!(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, readonly ? CheckFlags.Readonly : 0); result.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop); result.declarations = prop.declarations; result.nameType = getSymbolLinks(prop).nameType; result.syntheticOrigin = prop; return result; } function getIndexInfoWithReadonly(info: IndexInfo | undefined, readonly: boolean) { return info && info.isReadonly !== readonly ? createIndexInfo(info.type, readonly, info.declaration) : info; } function createLiteralType(flags: TypeFlags, value: string | number | PseudoBigInt, symbol: Symbol | undefined) { const type = createType(flags); type.symbol = symbol!; type.value = value; return type; } function getFreshTypeOfLiteralType(type: Type): Type { if (type.flags & TypeFlags.Literal) { if (!(type).freshType) { const freshType = createLiteralType(type.flags, (type).value, (type).symbol); freshType.regularType = type; freshType.freshType = freshType; (type).freshType = freshType; } return (type).freshType; } return type; } function getRegularTypeOfLiteralType(type: Type): Type { return type.flags & TypeFlags.Literal ? (type).regularType : type.flags & TypeFlags.Union ? ((type).regularType || ((type).regularType = getUnionType(sameMap((type).types, getRegularTypeOfLiteralType)) as UnionType)) : type; } function isFreshLiteralType(type: Type) { return !!(type.flags & TypeFlags.Literal) && (type).freshType === type; } function getLiteralType(value: string): StringLiteralType; function getLiteralType(value: string | number | PseudoBigInt, enumId?: number, symbol?: Symbol): LiteralType; function getLiteralType(value: string | number | PseudoBigInt, enumId?: number, symbol?: Symbol) { // We store all literal types in a single map with keys of the form '#NNN' and '@SSS', // where NNN is the text representation of a numeric literal and SSS are the characters // of a string literal. For literal enum members we use 'EEE#NNN' and 'EEE@SSS', where // EEE is a unique id for the containing enum type. const qualifier = typeof value === "number" ? "#" : typeof value === "string" ? "@" : "n"; const key = (enumId ? enumId : "") + qualifier + (typeof value === "object" ? pseudoBigIntToString(value) : value); let type = literalTypes.get(key); if (!type) { const flags = (typeof value === "number" ? TypeFlags.NumberLiteral : typeof value === "string" ? TypeFlags.StringLiteral : TypeFlags.BigIntLiteral) | (enumId ? TypeFlags.EnumLiteral : 0); literalTypes.set(key, type = createLiteralType(flags, value, symbol)); type.regularType = type; } return type; } function getTypeFromLiteralTypeNode(node: LiteralTypeNode): Type { if (node.literal.kind === SyntaxKind.NullKeyword) { return nullType; } const links = getNodeLinks(node); if (!links.resolvedType) { links.resolvedType = getRegularTypeOfLiteralType(checkExpression(node.literal)); } return links.resolvedType; } function createUniqueESSymbolType(symbol: Symbol) { const type = createType(TypeFlags.UniqueESSymbol); type.symbol = symbol; type.escapedName = `__@${type.symbol.escapedName}@${getSymbolId(type.symbol)}` as __String; return type; } function getESSymbolLikeTypeForNode(node: Node) { if (isValidESSymbolDeclaration(node)) { const symbol = getSymbolOfNode(node); const links = getSymbolLinks(symbol); return links.uniqueESSymbolType || (links.uniqueESSymbolType = createUniqueESSymbolType(symbol)); } return esSymbolType; } function getThisType(node: Node): Type { const container = getThisContainer(node, /*includeArrowFunctions*/ false); const parent = container && container.parent; if (parent && (isClassLike(parent) || parent.kind === SyntaxKind.InterfaceDeclaration)) { if (!hasSyntacticModifier(container, ModifierFlags.Static) && (!isConstructorDeclaration(container) || isNodeDescendantOf(node, container.body))) { return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent as ClassLikeDeclaration | InterfaceDeclaration)).thisType!; } } // inside x.prototype = { ... } if (parent && isObjectLiteralExpression(parent) && isBinaryExpression(parent.parent) && getAssignmentDeclarationKind(parent.parent) === AssignmentDeclarationKind.Prototype) { return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(parent.parent.left)!.parent!).thisType!; } // /** @return {this} */ // x.prototype.m = function() { ... } const host = node.flags & NodeFlags.JSDoc ? getHostSignatureFromJSDoc(node) : undefined; if (host && isFunctionExpression(host) && isBinaryExpression(host.parent) && getAssignmentDeclarationKind(host.parent) === AssignmentDeclarationKind.PrototypeProperty) { return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(host.parent.left)!.parent!).thisType!; } // inside constructor function C() { ... } if (isJSConstructor(container) && isNodeDescendantOf(node, container.body)) { return getDeclaredTypeOfClassOrInterface(getSymbolOfNode(container)).thisType!; } error(node, Diagnostics.A_this_type_is_available_only_in_a_non_static_member_of_a_class_or_interface); return errorType; } function getTypeFromThisTypeNode(node: ThisExpression | ThisTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { links.resolvedType = getThisType(node); } return links.resolvedType; } function getTypeFromRestTypeNode(node: RestTypeNode | NamedTupleMember) { return getTypeFromTypeNode(getArrayElementTypeNode(node.type) || node.type); } function getArrayElementTypeNode(node: TypeNode): TypeNode | undefined { switch (node.kind) { case SyntaxKind.ParenthesizedType: return getArrayElementTypeNode((node as ParenthesizedTypeNode).type); case SyntaxKind.TupleType: if ((node as TupleTypeNode).elements.length === 1) { node = (node as TupleTypeNode).elements[0]; if (node.kind === SyntaxKind.RestType || node.kind === SyntaxKind.NamedTupleMember && (node as NamedTupleMember).dotDotDotToken) { return getArrayElementTypeNode((node as RestTypeNode | NamedTupleMember).type); } } break; case SyntaxKind.ArrayType: return (node as ArrayTypeNode).elementType; } return undefined; } function getTypeFromNamedTupleTypeNode(node: NamedTupleMember): Type { const links = getNodeLinks(node); return links.resolvedType || (links.resolvedType = node.dotDotDotToken ? getTypeFromRestTypeNode(node) : node.questionToken && strictNullChecks ? getOptionalType(getTypeFromTypeNode(node.type)) : getTypeFromTypeNode(node.type)); } 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); case SyntaxKind.TypeReference: return getTypeFromTypeReference(node); case SyntaxKind.TypePredicate: return (node).assertsModifier ? voidType : booleanType; case SyntaxKind.ExpressionWithTypeArguments: return getTypeFromTypeReference(node); case SyntaxKind.TypeQuery: return getTypeFromTypeQueryNode(node); case SyntaxKind.ArrayType: case SyntaxKind.TupleType: return getTypeFromArrayOrTupleTypeNode(node); case SyntaxKind.OptionalType: return getTypeFromOptionalTypeNode(node); case SyntaxKind.UnionType: return getTypeFromUnionTypeNode(node); case SyntaxKind.IntersectionType: return getTypeFromIntersectionTypeNode(node); case SyntaxKind.JSDocNullableType: return getTypeFromJSDocNullableTypeNode(node); 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).type); case SyntaxKind.RestType: return getTypeFromRestTypeNode(node); case SyntaxKind.JSDocVariadicType: return getTypeFromJSDocVariadicType(node as JSDocVariadicType); case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: case SyntaxKind.TypeLiteral: case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.JSDocFunctionType: case SyntaxKind.JSDocSignature: return getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); case SyntaxKind.TypeOperator: return getTypeFromTypeOperatorNode(node); case SyntaxKind.IndexedAccessType: return getTypeFromIndexedAccessTypeNode(node); case SyntaxKind.MappedType: return getTypeFromMappedTypeNode(node); case SyntaxKind.ConditionalType: return getTypeFromConditionalTypeNode(node); case SyntaxKind.InferType: return getTypeFromInferTypeNode(node); case SyntaxKind.TemplateLiteralType: return getTypeFromTemplateTypeNode(node); case SyntaxKind.ImportType: return getTypeFromImportTypeNode(node); // This function assumes that an identifier, qualified name, or property access expression is a type expression // Callers should first ensure this by calling `isPartOfTypeNode` // TODO(rbuckton): These aren't valid TypeNodes, but we treat them as such because of `isPartOfTypeNode`, which returns `true` for things that aren't `TypeNode`s. case SyntaxKind.Identifier as TypeNodeSyntaxKind: case SyntaxKind.QualifiedName as TypeNodeSyntaxKind: case SyntaxKind.PropertyAccessExpression as TypeNodeSyntaxKind: const symbol = getSymbolAtLocation(node); return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; default: return errorType; } } function instantiateList(items: readonly T[], mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[]; function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined; function instantiateList(items: readonly T[] | undefined, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): readonly T[] | undefined { if (items && items.length) { for (let i = 0; i < items.length; i++) { const item = items[i]; const mapped = instantiator(item, mapper); if (item !== mapped) { const result = i === 0 ? [] : items.slice(0, i); result.push(mapped); for (i++; i < items.length; i++) { result.push(instantiator(items[i], mapper)); } return result; } } } return items; } function instantiateTypes(types: readonly Type[], mapper: TypeMapper): readonly Type[]; function instantiateTypes(types: readonly Type[] | undefined, mapper: TypeMapper): readonly Type[] | undefined; function instantiateTypes(types: readonly Type[] | undefined, mapper: TypeMapper): readonly Type[] | undefined { return instantiateList(types, mapper, instantiateType); } function instantiateSignatures(signatures: readonly Signature[], mapper: TypeMapper): readonly Signature[] { return instantiateList(signatures, mapper, instantiateSignature); } function createTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper { return sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) : makeArrayTypeMapper(sources, targets); } function getMappedType(type: Type, mapper: TypeMapper): Type { switch (mapper.kind) { case TypeMapKind.Simple: return type === mapper.source ? mapper.target : type; case TypeMapKind.Array: const sources = mapper.sources; const targets = mapper.targets; for (let i = 0; i < sources.length; i++) { if (type === sources[i]) { return targets ? targets[i] : anyType; } } return type; case TypeMapKind.Function: return mapper.func(type); case TypeMapKind.Composite: case TypeMapKind.Merged: const t1 = getMappedType(type, mapper.mapper1); return t1 !== type && mapper.kind === TypeMapKind.Composite ? instantiateType(t1, mapper.mapper2) : getMappedType(t1, mapper.mapper2); } } function makeUnaryTypeMapper(source: Type, target: Type): TypeMapper { return { kind: TypeMapKind.Simple, source, target }; } function makeArrayTypeMapper(sources: readonly TypeParameter[], targets: readonly Type[] | undefined): TypeMapper { return { kind: TypeMapKind.Array, sources, targets }; } function makeFunctionTypeMapper(func: (t: Type) => Type): TypeMapper { return { kind: TypeMapKind.Function, func }; } function makeCompositeTypeMapper(kind: TypeMapKind.Composite | TypeMapKind.Merged, mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper { return { kind, mapper1, mapper2 }; } function createTypeEraser(sources: readonly TypeParameter[]): TypeMapper { return createTypeMapper(sources, /*targets*/ undefined); } /** * Maps forward-references to later types parameters to the empty object type. * This is used during inference when instantiating type parameter defaults. */ function createBackreferenceMapper(context: InferenceContext, index: number): TypeMapper { return makeFunctionTypeMapper(t => findIndex(context.inferences, info => info.typeParameter === t) >= index ? unknownType : t); } function combineTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Composite, mapper1, mapper2) : mapper2; } function mergeTypeMappers(mapper1: TypeMapper | undefined, mapper2: TypeMapper): TypeMapper { return mapper1 ? makeCompositeTypeMapper(TypeMapKind.Merged, mapper1, mapper2) : mapper2; } function prependTypeMapping(source: Type, target: Type, mapper: TypeMapper | undefined) { return !mapper ? makeUnaryTypeMapper(source, target) : makeCompositeTypeMapper(TypeMapKind.Merged, makeUnaryTypeMapper(source, target), mapper); } function appendTypeMapping(mapper: TypeMapper | undefined, source: Type, target: Type) { return !mapper ? makeUnaryTypeMapper(source, target) : makeCompositeTypeMapper(TypeMapKind.Merged, mapper, makeUnaryTypeMapper(source, target)); } function getRestrictiveTypeParameter(tp: TypeParameter) { return tp.constraint === unknownType ? tp : tp.restrictiveInstantiation || ( tp.restrictiveInstantiation = createTypeParameter(tp.symbol), (tp.restrictiveInstantiation as TypeParameter).constraint = unknownType, tp.restrictiveInstantiation ); } function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter { const result = createTypeParameter(typeParameter.symbol); result.target = typeParameter; return result; } function instantiateTypePredicate(predicate: TypePredicate, mapper: TypeMapper): TypePredicate { return createTypePredicate(predicate.kind, predicate.parameterName, predicate.parameterIndex, instantiateType(predicate.type, mapper)); } function instantiateSignature(signature: Signature, mapper: TypeMapper, eraseTypeParameters?: boolean): Signature { let freshTypeParameters: TypeParameter[] | undefined; if (signature.typeParameters && !eraseTypeParameters) { // First create a fresh set of type parameters, then include a mapping from the old to the // new type parameters in the mapper function. Finally store this mapper in the new type // parameters such that we can use it when instantiating constraints. freshTypeParameters = map(signature.typeParameters, cloneTypeParameter); mapper = combineTypeMappers(createTypeMapper(signature.typeParameters, freshTypeParameters), mapper); for (const tp of freshTypeParameters) { tp.mapper = mapper; } } // Don't compute resolvedReturnType and resolvedTypePredicate now, // because using `mapper` now could trigger inferences to become fixed. (See `createInferenceContext`.) // See GH#17600. const result = createSignature(signature.declaration, freshTypeParameters, signature.thisParameter && instantiateSymbol(signature.thisParameter, mapper), instantiateList(signature.parameters, mapper, instantiateSymbol), /*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined, signature.minArgumentCount, signature.flags & SignatureFlags.PropagatingFlags); result.target = signature; result.mapper = mapper; return result; } function instantiateSymbol(symbol: Symbol, mapper: TypeMapper): Symbol { const links = getSymbolLinks(symbol); if (links.type && !couldContainTypeVariables(links.type)) { // If the type of the symbol is already resolved, and if that type could not possibly // be affected by instantiation, simply return the symbol itself. return symbol; } if (getCheckFlags(symbol) & CheckFlags.Instantiated) { // If symbol being instantiated is itself a instantiation, fetch the original target and combine the // type mappers. This ensures that original type identities are properly preserved and that aliases // always reference a non-aliases. symbol = links.target!; mapper = combineTypeMappers(links.mapper, mapper); } // Keep the flags from the symbol we're instantiating. Mark that is instantiated, and // also transient so that we can just store data on it directly. const result = createSymbol(symbol.flags, symbol.escapedName, CheckFlags.Instantiated | getCheckFlags(symbol) & (CheckFlags.Readonly | CheckFlags.Late | CheckFlags.OptionalParameter | CheckFlags.RestParameter)); result.declarations = symbol.declarations; result.parent = symbol.parent; result.target = symbol; result.mapper = mapper; if (symbol.valueDeclaration) { result.valueDeclaration = symbol.valueDeclaration; } if (links.nameType) { result.nameType = links.nameType; } return result; } function getObjectTypeInstantiation(type: AnonymousType | DeferredTypeReference, mapper: TypeMapper) { const declaration = type.objectFlags & ObjectFlags.Reference ? (type).node! : type.symbol.declarations[0]; const links = getNodeLinks(declaration); const target = type.objectFlags & ObjectFlags.Reference ? links.resolvedType! : 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; typeParameters = (target.objectFlags & ObjectFlags.Reference || target.symbol.flags & SymbolFlags.TypeLiteral) && !target.aliasTypeArguments ? filter(typeParameters, tp => isTypeParameterPossiblyReferenced(tp, declaration)) : 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 id = getTypeListId(typeArguments); if (!target.instantiations) { target.instantiations = new Map(); target.instantiations.set(getTypeListId(typeParameters), target); } let result = target.instantiations.get(id); if (!result) { const newMapper = createTypeMapper(typeParameters, typeArguments); result = target.objectFlags & ObjectFlags.Reference ? createDeferredTypeReference((type).target, (type).node, newMapper) : target.objectFlags & ObjectFlags.Mapped ? instantiateMappedType(target, newMapper) : instantiateAnonymousType(target, newMapper); target.instantiations.set(id, result); } return result; } return type; } function maybeTypeParameterReference(node: Node) { return !(node.kind === SyntaxKind.QualifiedName || node.parent.kind === SyntaxKind.TypeReference && (node.parent).typeArguments && node === (node.parent).typeName || node.parent.kind === SyntaxKind.ImportType && (node.parent as ImportTypeNode).typeArguments && node === (node.parent as ImportTypeNode).qualifier); } function isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node) { // If the type parameter doesn't have exactly one declaration, if there are invening statement blocks // between the node and the type parameter declaration, if the node contains actual references to the // type parameter, or if the node contains type queries, we consider the type parameter possibly referenced. if (tp.symbol && tp.symbol.declarations && tp.symbol.declarations.length === 1) { const container = tp.symbol.declarations[0].parent; for (let n = node; n !== container; n = n.parent) { if (!n || n.kind === SyntaxKind.Block || n.kind === SyntaxKind.ConditionalType && forEachChild((n).extendsType, containsReference)) { return true; } } return !!forEachChild(node, containsReference); } 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) === tp; // use worker because we're looking for === equality case SyntaxKind.TypeQuery: return true; } return !!forEachChild(node, containsReference); } } function getHomomorphicTypeVariable(type: MappedType) { const constraintType = getConstraintTypeFromMappedType(type); if (constraintType.flags & TypeFlags.Index) { const typeVariable = getActualTypeVariable((constraintType).type); if (typeVariable.flags & TypeFlags.TypeParameter) { return typeVariable; } } return undefined; } function instantiateMappedType(type: MappedType, mapper: TypeMapper): Type { // For a homomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping // operation depends on T as follows: // * If T is a primitive type no mapping is performed and the result is simply T. // * If T is a union type we distribute the mapped type over the union. // * If T is an array we map to an array where the element type has been transformed. // * If T is a tuple we map to a tuple where the element types have been transformed. // * Otherwise we map to an object type where the type of each property has been transformed. // For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } | // { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce // { [P in keyof A]: X } | undefined. const typeVariable = getHomomorphicTypeVariable(type); if (typeVariable) { const mappedTypeVariable = instantiateType(typeVariable, mapper); if (typeVariable !== mappedTypeVariable) { return mapType(getReducedType(mappedTypeVariable), t => { if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && t !== errorType) { if (!type.declaration.nameType) { if (isArrayType(t)) { return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper)); } if (isGenericTupleType(t)) { return instantiateMappedGenericTupleType(t, type, typeVariable, mapper); } if (isTupleType(t)) { return instantiateMappedTupleType(t, type, prependTypeMapping(typeVariable, t, mapper)); } } return instantiateAnonymousType(type, prependTypeMapping(typeVariable, t, mapper)); } return t; }); } } return instantiateAnonymousType(type, mapper); } function getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) { return modifiers & MappedTypeModifiers.IncludeReadonly ? true : modifiers & MappedTypeModifiers.ExcludeReadonly ? false : state; } function instantiateMappedGenericTupleType(tupleType: TupleTypeReference, mappedType: MappedType, typeVariable: TypeVariable, mapper: TypeMapper) { // When a tuple type is generic (i.e. when it contains variadic elements), we want to eagerly map the // non-generic elements and defer mapping the generic elements. In order to facilitate this, we transform // M<[A, B?, ...T, ...C[]] into [...M<[A]>, ...M<[B?]>, ...M, ...M] and then rely on tuple type // normalization to resolve the non-generic parts of the resulting tuple. const elementFlags = tupleType.target.elementFlags; const elementTypes = map(getTypeArguments(tupleType), (t, i) => { const singleton = elementFlags[i] & ElementFlags.Variadic ? t : elementFlags[i] & ElementFlags.Rest ? createArrayType(t) : createTupleType([t], [elementFlags[i]]); // The singleton is never a generic tuple type, so it is safe to recurse here. return instantiateMappedType(mappedType, prependTypeMapping(typeVariable, singleton, mapper)); }); const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, getMappedTypeModifiers(mappedType)); return createTupleType(elementTypes, map(elementTypes, _ => ElementFlags.Variadic), newReadonly); } function instantiateMappedArrayType(arrayType: Type, mappedType: MappedType, mapper: TypeMapper) { const elementType = instantiateMappedTypeTemplate(mappedType, numberType, /*isOptional*/ true, mapper); return elementType === errorType ? errorType : createArrayType(elementType, getModifiedReadonlyState(isReadonlyArrayType(arrayType), getMappedTypeModifiers(mappedType))); } function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) { const elementFlags = tupleType.target.elementFlags; const elementTypes = map(getTypeArguments(tupleType), (_, i) => instantiateMappedTypeTemplate(mappedType, getLiteralType("" + i), !!(elementFlags[i] & ElementFlags.Optional), mapper)); const modifiers = getMappedTypeModifiers(mappedType); const newTupleModifiers = modifiers & MappedTypeModifiers.IncludeOptional ? map(elementFlags, f => f & ElementFlags.Required ? ElementFlags.Optional : f) : modifiers & MappedTypeModifiers.ExcludeOptional ? map(elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) : elementFlags; const newReadonly = getModifiedReadonlyState(tupleType.target.readonly, modifiers); return contains(elementTypes, errorType) ? errorType : createTupleType(elementTypes, newTupleModifiers, newReadonly, tupleType.target.labeledElementDeclarations); } function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) { const templateMapper = appendTypeMapping(mapper, getTypeParameterFromMappedType(type), key); const propType = instantiateType(getTemplateTypeFromMappedType(type.target || type), templateMapper); const modifiers = getMappedTypeModifiers(type); return strictNullChecks && modifiers & MappedTypeModifiers.IncludeOptional && !maybeTypeOfKind(propType, TypeFlags.Undefined | TypeFlags.Void) ? getOptionalType(propType) : strictNullChecks && modifiers & MappedTypeModifiers.ExcludeOptional && isOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) : propType; } function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper): AnonymousType { const result = createObjectType(type.objectFlags | ObjectFlags.Instantiated, type.symbol); if (type.objectFlags & ObjectFlags.Mapped) { (result).declaration = (type).declaration; // C.f. instantiateSignature const origTypeParameter = getTypeParameterFromMappedType(type); const freshTypeParameter = cloneTypeParameter(origTypeParameter); (result).typeParameter = freshTypeParameter; mapper = combineTypeMappers(makeUnaryTypeMapper(origTypeParameter, freshTypeParameter), mapper); freshTypeParameter.mapper = mapper; } result.target = type; result.mapper = mapper; result.aliasSymbol = type.aliasSymbol; result.aliasTypeArguments = instantiateTypes(type.aliasTypeArguments, mapper); return result; } function getConditionalTypeInstantiation(type: ConditionalType, mapper: TypeMapper): Type { const root = type.root; if (root.outerTypeParameters) { // We are instantiating a conditional type that has one or more type parameters in scope. Apply the // mapper to the type parameters to produce the effective list of type arguments, and compute the // instantiation cache key from the type IDs of the type arguments. const typeArguments = map(root.outerTypeParameters, t => getMappedType(t, mapper)); const id = getTypeListId(typeArguments); let result = root.instantiations!.get(id); if (!result) { const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments); result = instantiateConditionalType(root, newMapper); root.instantiations!.set(id, result); } return result; } return type; } function instantiateConditionalType(root: ConditionalRoot, mapper: TypeMapper): Type { // Check if we have a conditional type where the check type is a naked type parameter. If so, // the conditional type is distributive over union types and when T is instantiated to a union // type A | B, we produce (A extends U ? X : Y) | (B extends U ? X : Y). if (root.isDistributive) { const checkType = root.checkType; const instantiatedType = getMappedType(checkType, mapper); if (checkType !== instantiatedType && instantiatedType.flags & (TypeFlags.Union | TypeFlags.Never)) { return mapType(instantiatedType, t => getConditionalType(root, prependTypeMapping(checkType, t, mapper))); } } return getConditionalType(root, mapper); } 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 { if (!(type && mapper && couldContainTypeVariables(type))) { return type; } if (instantiationDepth === 50 || instantiationCount >= 5000000) { // We have reached 50 recursive type instantiations and there is a very high likelyhood we're dealing // with a combination of infinite generic types that perpetually generate new type identities. We stop // the recursion here by yielding the error type. tracing.instant(tracing.Phase.Check, "instantiateType_DepthLimit", { typeId: type.id, instantiationDepth, instantiationCount }); error(currentNode, Diagnostics.Type_instantiation_is_excessively_deep_and_possibly_infinite); return errorType; } totalInstantiationCount++; instantiationCount++; instantiationDepth++; const result = instantiateTypeWorker(type, mapper); instantiationDepth--; return result; } function instantiateTypeWorker(type: Type, mapper: TypeMapper): Type { const flags = type.flags; if (flags & TypeFlags.TypeParameter) { return getMappedType(type, mapper); } if (flags & TypeFlags.Object) { const objectFlags = (type).objectFlags; if (objectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous | ObjectFlags.Mapped)) { if (objectFlags & ObjectFlags.Reference && !((type).node)) { const resolvedTypeArguments = (type).resolvedTypeArguments; const newTypeArguments = instantiateTypes(resolvedTypeArguments, mapper); return newTypeArguments !== resolvedTypeArguments ? createNormalizedTypeReference((type).target, newTypeArguments) : type; } return getObjectTypeInstantiation(type, mapper); } return type; } if (flags & TypeFlags.UnionOrIntersection) { const types = (type).types; const newTypes = instantiateTypes(types, mapper); return newTypes === types ? type : flags & TypeFlags.Intersection ? getIntersectionType(newTypes, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : getUnionType(newTypes, UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)); } if (flags & TypeFlags.Index) { return getIndexType(instantiateType((type).type, mapper)); } if (flags & TypeFlags.TemplateLiteral) { return getTemplateLiteralType((type).texts, instantiateTypes((type).types, mapper)); } if (flags & TypeFlags.StringMapping) { return getStringMappingType((type).symbol, instantiateType((type).type, mapper)); } if (flags & TypeFlags.IndexedAccess) { return getIndexedAccessType(instantiateType((type).objectType, mapper), instantiateType((type).indexType, mapper), (type).noUncheckedIndexedAccessCandidate, /*accessNode*/ undefined, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)); } if (flags & TypeFlags.Conditional) { return getConditionalTypeInstantiation(type, combineTypeMappers((type).mapper, mapper)); } if (flags & TypeFlags.Substitution) { const maybeVariable = instantiateType((type).baseType, mapper); if (maybeVariable.flags & TypeFlags.TypeVariable) { return getSubstitutionType(maybeVariable as TypeVariable, instantiateType((type).substitute, mapper)); } else { const sub = instantiateType((type).substitute, mapper); if (sub.flags & TypeFlags.AnyOrUnknown || isTypeAssignableTo(getRestrictiveInstantiation(maybeVariable), getRestrictiveInstantiation(sub))) { return maybeVariable; } return sub; } } return type; } 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 | undefined, mapper: TypeMapper): IndexInfo | undefined { return info && createIndexInfo(instantiateType(info.type, mapper), info.isReadonly, info.declaration); } // Returns true if the given expression contains (at any level of nesting) a function or arrow expression // that is subject to contextual typing. function isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike | JsxChild): boolean { Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); switch (node.kind) { case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.MethodDeclaration: case SyntaxKind.FunctionDeclaration: // Function declarations can have context when annotated with a jsdoc @type return isContextSensitiveFunctionLikeDeclaration(node); case SyntaxKind.ObjectLiteralExpression: return some((node).properties, isContextSensitive); case SyntaxKind.ArrayLiteralExpression: return some((node).elements, isContextSensitive); case SyntaxKind.ConditionalExpression: return isContextSensitive((node).whenTrue) || isContextSensitive((node).whenFalse); case SyntaxKind.BinaryExpression: return ((node).operatorToken.kind === SyntaxKind.BarBarToken || (node).operatorToken.kind === SyntaxKind.QuestionQuestionToken) && (isContextSensitive((node).left) || isContextSensitive((node).right)); case SyntaxKind.PropertyAssignment: return isContextSensitive((node).initializer); case SyntaxKind.ParenthesizedExpression: return isContextSensitive((node).expression); case SyntaxKind.JsxAttributes: return some((node).properties, isContextSensitive) || isJsxOpeningElement(node.parent) && some(node.parent.parent.children, isContextSensitive); case SyntaxKind.JsxAttribute: { // If there is no initializer, JSX attribute has a boolean value of true which is not context sensitive. const { initializer } = node as JsxAttribute; return !!initializer && isContextSensitive(initializer); } case SyntaxKind.JsxExpression: { // It is possible to that node.expression is undefined (e.g

) const { expression } = node as JsxExpression; return !!expression && isContextSensitive(expression); } } return false; } function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean { return (!isFunctionDeclaration(node) || isInJSFile(node) && !!getTypeForDeclarationFromJSDocComment(node)) && (hasContextSensitiveParameters(node) || hasContextSensitiveReturnExpression(node)); } function hasContextSensitiveParameters(node: FunctionLikeDeclaration) { // Functions with type parameters are not context sensitive. if (!node.typeParameters) { // Functions with any parameters that lack type annotations are context sensitive. if (some(node.parameters, p => !getEffectiveTypeAnnotationNode(p))) { return true; } if (node.kind !== SyntaxKind.ArrowFunction) { // If the first parameter is not an explicit 'this' parameter, then the function has // an implicit 'this' parameter which is subject to contextual typing. const parameter = firstOrUndefined(node.parameters); if (!(parameter && parameterIsThisKeyword(parameter))) { return true; } } } return false; } function hasContextSensitiveReturnExpression(node: FunctionLikeDeclaration) { // TODO(anhans): A block should be context-sensitive if it has a context-sensitive return value. return !node.typeParameters && !getEffectiveReturnTypeNode(node) && !!node.body && node.body.kind !== SyntaxKind.Block && isContextSensitive(node.body); } function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | ArrowFunction | MethodDeclaration { return (isInJSFile(func) && isFunctionDeclaration(func) || isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && isContextSensitiveFunctionLikeDeclaration(func); } function getTypeWithoutSignatures(type: Type): Type { if (type.flags & TypeFlags.Object) { const resolved = resolveStructuredTypeMembers(type); 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; return result; } } else if (type.flags & TypeFlags.Intersection) { return getIntersectionType(map((type).types, getTypeWithoutSignatures)); } return type; } // TYPE CHECKING function isTypeIdenticalTo(source: Type, target: Type): boolean { return isTypeRelatedTo(source, target, identityRelation); } function compareTypesIdentical(source: Type, target: Type): Ternary { return isTypeRelatedTo(source, target, identityRelation) ? Ternary.True : Ternary.False; } function compareTypesAssignable(source: Type, target: Type): Ternary { return isTypeRelatedTo(source, target, assignableRelation) ? Ternary.True : Ternary.False; } function compareTypesSubtypeOf(source: Type, target: Type): Ternary { return isTypeRelatedTo(source, target, subtypeRelation) ? Ternary.True : Ternary.False; } function isTypeSubtypeOf(source: Type, target: Type): boolean { return isTypeRelatedTo(source, target, subtypeRelation); } function isTypeAssignableTo(source: Type, target: Type): boolean { return isTypeRelatedTo(source, target, assignableRelation); } // An object type S is considered to be derived from an object type T if // S is a union type and every constituent of S is derived from T, // T is a union type and S is derived from at least one constituent of T, or // S is a type variable with a base constraint that is derived from T, // T is one of the global types Object and Function and S is a subtype of T, or // T occurs directly or indirectly in an 'extends' clause of S. // Note that this check ignores type parameters and only considers the // inheritance hierarchy. function isTypeDerivedFrom(source: Type, target: Type): boolean { return source.flags & TypeFlags.Union ? every((source).types, t => isTypeDerivedFrom(t, target)) : target.flags & TypeFlags.Union ? some((target).types, t => isTypeDerivedFrom(source, t)) : source.flags & TypeFlags.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) : target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) : target === globalFunctionType ? !!(source.flags & TypeFlags.Object) && isFunctionObjectType(source as ObjectType) : hasBaseType(source, getTargetType(target)) || (isArrayType(target) && !isReadonlyArrayType(target) && isTypeDerivedFrom(source, globalReadonlyArrayType)); } /** * This is *not* a bi-directional relationship. * If one needs to check both directions for comparability, use a second call to this function or 'checkTypeComparableTo'. * * A type S is comparable to a type T if some (but not necessarily all) of the possible values of S are also possible values of T. * It is used to check following cases: * - the types of the left and right sides of equality/inequality operators (`===`, `!==`, `==`, `!=`). * - the types of `case` clause expressions and their respective `switch` expressions. * - the type of an expression in a type assertion with the type being asserted. */ function isTypeComparableTo(source: Type, target: Type): boolean { return isTypeRelatedTo(source, target, comparableRelation); } function areTypesComparable(type1: Type, type2: Type): boolean { return isTypeComparableTo(type1, type2) || isTypeComparableTo(type2, type1); } function checkTypeAssignableTo(source: Type, target: Type, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputObject?: { errors?: Diagnostic[] }): boolean { return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain, errorOutputObject); } /** * Like `checkTypeAssignableTo`, but if it would issue an error, instead performs structural comparisons of the types using the given expression node to * attempt to issue more specific errors on, for example, specific object literal properties or tuple members. */ function checkTypeAssignableToAndOptionallyElaborate(source: Type, target: Type, errorNode: Node | undefined, expr: Expression | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { return checkTypeRelatedToAndOptionallyElaborate(source, target, assignableRelation, errorNode, expr, headMessage, containingMessageChain, /*errorOutputContainer*/ undefined); } function checkTypeRelatedToAndOptionallyElaborate( source: Type, target: Type, relation: ESMap, errorNode: Node | undefined, expr: Expression | undefined, headMessage: DiagnosticMessage | undefined, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined ): boolean { if (isTypeRelatedTo(source, target, relation)) return true; if (!errorNode || !elaborateError(expr, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { return checkTypeRelatedTo(source, target, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer); } return false; } function isOrHasGenericConditional(type: Type): boolean { return !!(type.flags & TypeFlags.Conditional || (type.flags & TypeFlags.Intersection && some((type as IntersectionType).types, isOrHasGenericConditional))); } function elaborateError( node: Expression | undefined, source: Type, target: Type, relation: ESMap, headMessage: DiagnosticMessage | undefined, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined ): boolean { if (!node || isOrHasGenericConditional(target)) return false; if (!checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined) && elaborateDidYouMeanToCallOrConstruct(node, source, target, relation, headMessage, containingMessageChain, errorOutputContainer)) { return true; } switch (node.kind) { case SyntaxKind.JsxExpression: case SyntaxKind.ParenthesizedExpression: return elaborateError((node as ParenthesizedExpression | JsxExpression).expression, source, target, relation, headMessage, containingMessageChain, errorOutputContainer); case SyntaxKind.BinaryExpression: switch ((node as BinaryExpression).operatorToken.kind) { case SyntaxKind.EqualsToken: case SyntaxKind.CommaToken: return elaborateError((node as BinaryExpression).right, source, target, relation, headMessage, containingMessageChain, errorOutputContainer); } break; case SyntaxKind.ObjectLiteralExpression: return elaborateObjectLiteral(node as ObjectLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer); case SyntaxKind.ArrayLiteralExpression: return elaborateArrayLiteral(node as ArrayLiteralExpression, source, target, relation, containingMessageChain, errorOutputContainer); case SyntaxKind.JsxAttributes: return elaborateJsxComponents(node as JsxAttributes, source, target, relation, containingMessageChain, errorOutputContainer); case SyntaxKind.ArrowFunction: return elaborateArrowFunction(node as ArrowFunction, source, target, relation, containingMessageChain, errorOutputContainer); } return false; } function elaborateDidYouMeanToCallOrConstruct( node: Expression, source: Type, target: Type, relation: ESMap, headMessage: DiagnosticMessage | undefined, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined ): boolean { const callSignatures = getSignaturesOfType(source, SignatureKind.Call); const constructSignatures = getSignaturesOfType(source, SignatureKind.Construct); for (const signatures of [constructSignatures, callSignatures]) { if (some(signatures, s => { const returnType = getReturnTypeOfSignature(s); return !(returnType.flags & (TypeFlags.Any | TypeFlags.Never)) && checkTypeRelatedTo(returnType, target, relation, /*errorNode*/ undefined); })) { const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; checkTypeAssignableTo(source, target, node, headMessage, containingMessageChain, resultObj); const diagnostic = resultObj.errors![resultObj.errors!.length - 1]; addRelatedInfo(diagnostic, createDiagnosticForNode( node, signatures === constructSignatures ? Diagnostics.Did_you_mean_to_use_new_with_this_expression : Diagnostics.Did_you_mean_to_call_this_expression )); return true; } } return false; } function elaborateArrowFunction( node: ArrowFunction, source: Type, target: Type, relation: ESMap, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined ): boolean { // Don't elaborate blocks if (isBlock(node.body)) { return false; } // Or functions with annotated parameter types if (some(node.parameters, ts.hasType)) { return false; } const sourceSig = getSingleCallSignature(source); if (!sourceSig) { return false; } const targetSignatures = getSignaturesOfType(target, SignatureKind.Call); if (!length(targetSignatures)) { return false; } const returnExpression = node.body; const sourceReturn = getReturnTypeOfSignature(sourceSig); const targetReturn = getUnionType(map(targetSignatures, getReturnTypeOfSignature)); if (!checkTypeRelatedTo(sourceReturn, targetReturn, relation, /*errorNode*/ undefined)) { const elaborated = returnExpression && elaborateError(returnExpression, sourceReturn, targetReturn, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); if (elaborated) { return elaborated; } const resultObj: { errors?: Diagnostic[] } = errorOutputContainer || {}; checkTypeRelatedTo(sourceReturn, targetReturn, relation, returnExpression, /*message*/ undefined, containingMessageChain, resultObj); if (resultObj.errors) { if (target.symbol && length(target.symbol.declarations)) { addRelatedInfo(resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode( target.symbol.declarations[0], Diagnostics.The_expected_type_comes_from_the_return_type_of_this_signature, )); } if ((getFunctionFlags(node) & FunctionFlags.Async) === 0 // exclude cases where source itself is promisy - this way we don't make a suggestion when relating // an IPromise and a Promise that are slightly different && !getTypeOfPropertyOfType(sourceReturn, "then" as __String) && checkTypeRelatedTo(createPromiseType(sourceReturn), targetReturn, relation, /*errorNode*/ undefined) ) { addRelatedInfo(resultObj.errors[resultObj.errors.length - 1], createDiagnosticForNode( node, Diagnostics.Did_you_mean_to_mark_this_function_as_async )); } return true; } } return false; } function getBestMatchIndexedAccessTypeOrUndefined(source: Type, target: Type, nameType: Type) { const idx = getIndexedAccessTypeOrUndefined(target, nameType); if (idx) { return idx; } if (target.flags & TypeFlags.Union) { const best = getBestMatchingType(source, target as UnionType); if (best) { return getIndexedAccessTypeOrUndefined(best, nameType); } } } function checkExpressionForMutableLocationWithContextualType(next: Expression, sourcePropType: Type) { next.contextualType = sourcePropType; try { return checkExpressionForMutableLocation(next, CheckMode.Contextual, sourcePropType); } finally { next.contextualType = undefined; } } type ElaborationIterator = IterableIterator<{ errorNode: Node, innerExpression: Expression | undefined, nameType: Type, errorMessage?: DiagnosticMessage | undefined }>; /** * For every element returned from the iterator, checks that element to issue an error on a property of that element's type * If that element would issue an error, we first attempt to dive into that element's inner expression and issue a more specific error by recuring into `elaborateError` * Otherwise, we issue an error on _every_ element which fail the assignability check */ function elaborateElementwise( iterator: ElaborationIterator, source: Type, target: Type, relation: ESMap, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined ) { // Assignability failure - check each prop individually, and if that fails, fall back on the bad error span let reportedError = false; for (let status = iterator.next(); !status.done; status = iterator.next()) { const { errorNode: prop, innerExpression: next, nameType, errorMessage } = status.value; const targetPropType = getBestMatchIndexedAccessTypeOrUndefined(source, target, nameType); if (!targetPropType || targetPropType.flags & TypeFlags.IndexedAccess) continue; // Don't elaborate on indexes on generic variables const sourcePropType = getIndexedAccessTypeOrUndefined(source, nameType); if (sourcePropType && !checkTypeRelatedTo(sourcePropType, targetPropType, relation, /*errorNode*/ undefined)) { const elaborated = next && elaborateError(next, sourcePropType, targetPropType, relation, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); if (elaborated) { reportedError = true; } else { // 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; 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 = isTypeAssignableToKind(nameType, TypeFlags.NumberLike) && getIndexInfoOfType(target, IndexKind.Number) || getIndexInfoOfType(target, IndexKind.String) || undefined; 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) )); } } } reportedError = true; } } } return reportedError; } function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator { if (!length(node.properties)) return; for (const prop of node.properties) { if (isJsxSpreadAttribute(prop)) continue; yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getLiteralType(idText(prop.name)) }; } } function *generateJsxChildren(node: JsxElement, getInvalidTextDiagnostic: () => DiagnosticMessage): ElaborationIterator { if (!length(node.children)) return; let memberOffset = 0; for (let i = 0; i < node.children.length; i++) { const child = node.children[i]; const nameType = getLiteralType(i - memberOffset); const elem = getElaborationElementForJsxChild(child, nameType, getInvalidTextDiagnostic); if (elem) { yield elem; } else { memberOffset++; } } } function getElaborationElementForJsxChild(child: JsxChild, nameType: LiteralType, getInvalidTextDiagnostic: () => DiagnosticMessage) { switch (child.kind) { case SyntaxKind.JsxExpression: // child is of the type of the expression return { errorNode: child, innerExpression: child.expression, nameType }; case SyntaxKind.JsxText: if (child.containsOnlyTriviaWhiteSpaces) { break; // Whitespace only jsx text isn't real jsx text } // child is a string return { errorNode: child, innerExpression: undefined, nameType, errorMessage: getInvalidTextDiagnostic() }; case SyntaxKind.JsxElement: case SyntaxKind.JsxSelfClosingElement: case SyntaxKind.JsxFragment: // child is of type JSX.Element return { errorNode: child, innerExpression: child, nameType }; default: return Debug.assertNever(child, "Found invalid jsx child"); } } function elaborateJsxComponents( node: JsxAttributes, source: Type, target: Type, relation: ESMap, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined ) { let result = elaborateElementwise(generateJsxAttributes(node), source, target, relation, containingMessageChain, errorOutputContainer); let invalidTextDiagnostic: DiagnosticMessage | undefined; if (isJsxOpeningElement(node.parent) && isJsxElement(node.parent.parent)) { const containingElement = node.parent.parent; const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); const childrenNameType = getLiteralType(childrenPropName); const childrenTargetType = getIndexedAccessType(target, childrenNameType); const validChildren = getSemanticJsxChildren(containingElement.children); if (!length(validChildren)) { return result; } const moreThanOneRealChildren = length(validChildren) > 1; const arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType); const nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t)); if (moreThanOneRealChildren) { if (arrayLikeTargetParts !== neverType) { const realSource = createTupleType(checkJsxChildren(containingElement, CheckMode.Normal)); const children = generateJsxChildren(containingElement, getInvalidTextualChildDiagnostic); result = elaborateElementwise(children, realSource, arrayLikeTargetParts, relation, containingMessageChain, errorOutputContainer) || result; } else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) { // arity mismatch result = true; const diag = error( containingElement.openingElement.tagName, Diagnostics.This_JSX_tag_s_0_prop_expects_a_single_child_of_type_1_but_multiple_children_were_provided, childrenPropName, typeToString(childrenTargetType) ); if (errorOutputContainer && errorOutputContainer.skipLogging) { (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); } } } else { if (nonArrayLikeTargetParts !== neverType) { const child = validChildren[0]; const elem = getElaborationElementForJsxChild(child, childrenNameType, getInvalidTextualChildDiagnostic); if (elem) { result = elaborateElementwise( (function*() { yield elem; })(), source, target, relation, containingMessageChain, errorOutputContainer ) || result; } } else if (!isTypeRelatedTo(getIndexedAccessType(source, childrenNameType), childrenTargetType, relation)) { // arity mismatch result = true; const diag = error( containingElement.openingElement.tagName, Diagnostics.This_JSX_tag_s_0_prop_expects_type_1_which_requires_multiple_children_but_only_a_single_child_was_provided, childrenPropName, typeToString(childrenTargetType) ); if (errorOutputContainer && errorOutputContainer.skipLogging) { (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); } } } } return result; function getInvalidTextualChildDiagnostic() { if (!invalidTextDiagnostic) { const tagNameText = getTextOfNode(node.parent.tagName); const childPropName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); const childrenPropName = childPropName === undefined ? "children" : unescapeLeadingUnderscores(childPropName); const childrenTargetType = getIndexedAccessType(target, getLiteralType(childrenPropName)); const diagnostic = Diagnostics._0_components_don_t_accept_text_as_child_elements_Text_in_JSX_has_the_type_string_but_the_expected_type_of_1_is_2; invalidTextDiagnostic = { ...diagnostic, key: "!!ALREADY FORMATTED!!", message: formatMessage(/*_dummy*/ undefined, diagnostic, tagNameText, childrenPropName, typeToString(childrenTargetType)) }; } return invalidTextDiagnostic; } } function *generateLimitedTupleElements(node: ArrayLiteralExpression, target: Type): ElaborationIterator { const len = length(node.elements); if (!len) return; for (let i = 0; i < len; i++) { // Skip elements which do not exist in the target - a length error on the tuple overall is likely better than an error on a mismatched index signature if (isTupleLikeType(target) && !getPropertyOfType(target, ("" + i) as __String)) continue; const elem = node.elements[i]; if (isOmittedExpression(elem)) continue; const nameType = getLiteralType(i); yield { errorNode: elem, innerExpression: elem, nameType }; } } function elaborateArrayLiteral( node: ArrayLiteralExpression, source: Type, target: Type, relation: ESMap, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined ) { if (target.flags & TypeFlags.Primitive) return false; if (isTupleLikeType(source)) { return elaborateElementwise(generateLimitedTupleElements(node, target), source, target, relation, containingMessageChain, errorOutputContainer); } // recreate a tuple from the elements, if possible // Since we're re-doing the expression type, we need to reapply the contextual type const oldContext = node.contextualType; node.contextualType = target; try { const tupleizedType = checkArrayLiteral(node, CheckMode.Contextual, /*forceTuple*/ true); node.contextualType = oldContext; if (isTupleLikeType(tupleizedType)) { return elaborateElementwise(generateLimitedTupleElements(node, target), tupleizedType, target, relation, containingMessageChain, errorOutputContainer); } return false; } finally { node.contextualType = oldContext; } } function *generateObjectLiteralElements(node: ObjectLiteralExpression): ElaborationIterator { if (!length(node.properties)) return; for (const prop of node.properties) { if (isSpreadAssignment(prop)) continue; const type = getLiteralTypeFromProperty(getSymbolOfNode(prop), TypeFlags.StringOrNumberLiteralOrUnique); if (!type || (type.flags & TypeFlags.Never)) { continue; } switch (prop.kind) { case SyntaxKind.SetAccessor: case SyntaxKind.GetAccessor: case SyntaxKind.MethodDeclaration: case SyntaxKind.ShorthandPropertyAssignment: yield { errorNode: prop.name, innerExpression: undefined, nameType: type }; break; case SyntaxKind.PropertyAssignment: yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: type, errorMessage: isComputedNonLiteralName(prop.name) ? Diagnostics.Type_of_computed_property_s_value_is_0_which_is_not_assignable_to_type_1 : undefined }; break; default: Debug.assertNever(prop); } } } function elaborateObjectLiteral( node: ObjectLiteralExpression, source: Type, target: Type, relation: ESMap, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined ) { if (target.flags & TypeFlags.Primitive) return false; return elaborateElementwise(generateObjectLiteralElements(node), source, target, relation, containingMessageChain, errorOutputContainer); } /** * This is *not* a bi-directional relationship. * If one needs to check both directions for comparability, use a second call to this function or 'isTypeComparableTo'. */ function checkTypeComparableTo(source: Type, target: Type, errorNode: Node, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined): boolean { return checkTypeRelatedTo(source, target, comparableRelation, errorNode, headMessage, containingMessageChain); } function isSignatureAssignableTo(source: Signature, target: Signature, ignoreReturnTypes: boolean): boolean { return compareSignaturesRelated(source, target, ignoreReturnTypes ? SignatureCheckMode.IgnoreReturnTypes : 0, /*reportErrors*/ false, /*errorReporter*/ undefined, /*errorReporter*/ undefined, compareTypesAssignable, /*reportUnreliableMarkers*/ undefined) !== Ternary.False; } type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void; /** * Returns true if `s` is `(...args: any[]) => any` or `(this: any, ...args: any[]) => any` */ function isAnySignature(s: Signature) { return !s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 && signatureHasRestParameter(s) && (getTypeOfParameter(s.parameters[0]) === anyArrayType || isTypeAny(getTypeOfParameter(s.parameters[0]))) && isTypeAny(getReturnTypeOfSignature(s)); } /** * See signatureRelatedTo, compareSignaturesIdentical */ function compareSignaturesRelated(source: Signature, target: Signature, checkMode: SignatureCheckMode, reportErrors: boolean, errorReporter: ErrorReporter | undefined, incompatibleErrorReporter: ((source: Type, target: Type) => void) | undefined, compareTypes: TypeComparer, reportUnreliableMarkers: TypeMapper | undefined): Ternary { // TODO (drosen): De-duplicate code between related functions. if (source === target) { return Ternary.True; } if (isAnySignature(target)) { return Ternary.True; } const targetCount = getParameterCount(target); const sourceHasMoreParameters = !hasEffectiveRestParameter(target) && (checkMode & SignatureCheckMode.StrictArity ? hasEffectiveRestParameter(source) || getParameterCount(source) > targetCount : getMinArgumentCount(source) > targetCount); if (sourceHasMoreParameters) { return Ternary.False; } if (source.typeParameters && source.typeParameters !== target.typeParameters) { target = getCanonicalSignature(target); source = instantiateSignatureInContextOf(source, target, /*inferenceContext*/ undefined, compareTypes); } const sourceCount = getParameterCount(source); const sourceRestType = getNonArrayRestType(source); const targetRestType = getNonArrayRestType(target); if (sourceRestType || targetRestType) { void instantiateType(sourceRestType || targetRestType, reportUnreliableMarkers); } if (sourceRestType && targetRestType && sourceCount !== targetCount) { // We're not able to relate misaligned complex rest parameters return Ternary.False; } const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; const strictVariance = !(checkMode & SignatureCheckMode.Callback) && strictFunctionTypes && kind !== SyntaxKind.MethodDeclaration && kind !== SyntaxKind.MethodSignature && kind !== SyntaxKind.Constructor; let result = Ternary.True; const sourceThisType = getThisTypeOfSignature(source); if (sourceThisType && sourceThisType !== voidType) { const targetThisType = getThisTypeOfSignature(target); if (targetThisType) { // void sources are assignable to anything. const related = !strictVariance && compareTypes(sourceThisType, targetThisType, /*reportErrors*/ false) || compareTypes(targetThisType, sourceThisType, reportErrors); if (!related) { if (reportErrors) { errorReporter!(Diagnostics.The_this_types_of_each_signature_are_incompatible); } return Ternary.False; } result &= related; } } const paramCount = sourceRestType || targetRestType ? Math.min(sourceCount, targetCount) : Math.max(sourceCount, targetCount); const restIndex = sourceRestType || targetRestType ? paramCount - 1 : -1; for (let i = 0; i < paramCount; i++) { const sourceType = i === restIndex ? getRestTypeAtPosition(source, i) : tryGetTypeAtPosition(source, i); const targetType = i === restIndex ? getRestTypeAtPosition(target, i) : tryGetTypeAtPosition(target, i); if (sourceType && targetType) { // In order to ensure that any generic type Foo is at least co-variant with respect to T no matter // how Foo uses T, we need to relate parameters bi-variantly (given that parameters are input positions, // they naturally relate only contra-variantly). However, if the source and target parameters both have // function types with a single call signature, we know we are relating two callback parameters. In // that case it is sufficient to only relate the parameters of the signatures co-variantly because, // similar to return values, callback parameters are output positions. This means that a Promise, // where T is used only in callback parameter positions, will be co-variant (as opposed to bi-variant) // with respect to T. const sourceSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(sourceType)); const targetSig = checkMode & SignatureCheckMode.Callback ? undefined : getSingleCallSignature(getNonNullableType(targetType)); const callbacks = sourceSig && targetSig && !getTypePredicateOfSignature(sourceSig) && !getTypePredicateOfSignature(targetSig) && (getFalsyFlags(sourceType) & TypeFlags.Nullable) === (getFalsyFlags(targetType) & TypeFlags.Nullable); let related = callbacks ? compareSignaturesRelated(targetSig!, sourceSig!, (checkMode & SignatureCheckMode.StrictArity) | (strictVariance ? SignatureCheckMode.StrictCallback : SignatureCheckMode.BivariantCallback), reportErrors, errorReporter, incompatibleErrorReporter, compareTypes, reportUnreliableMarkers) : !(checkMode & SignatureCheckMode.Callback) && !strictVariance && compareTypes(sourceType, targetType, /*reportErrors*/ false) || compareTypes(targetType, sourceType, reportErrors); // With strict arity, (x: number | undefined) => void is a subtype of (x?: number | undefined) => void if (related && checkMode & SignatureCheckMode.StrictArity && i >= getMinArgumentCount(source) && i < getMinArgumentCount(target) && compareTypes(sourceType, targetType, /*reportErrors*/ false)) { related = Ternary.False; } if (!related) { if (reportErrors) { errorReporter!(Diagnostics.Types_of_parameters_0_and_1_are_incompatible, unescapeLeadingUnderscores(getParameterNameAtPosition(source, i)), unescapeLeadingUnderscores(getParameterNameAtPosition(target, i))); } return Ternary.False; } result &= related; } } if (!(checkMode & SignatureCheckMode.IgnoreReturnTypes)) { // If a signature resolution is already in-flight, skip issuing a circularity error // here and just use the `any` type directly const targetReturnType = isResolvingReturnTypeOfSignature(target) ? anyType : target.declaration && isJSConstructor(target.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(target.declaration.symbol)) : getReturnTypeOfSignature(target); if (targetReturnType === voidType) { return result; } const sourceReturnType = isResolvingReturnTypeOfSignature(source) ? anyType : source.declaration && isJSConstructor(source.declaration) ? getDeclaredTypeOfClassOrInterface(getMergedSymbol(source.declaration.symbol)) : getReturnTypeOfSignature(source); // The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions const targetTypePredicate = getTypePredicateOfSignature(target); if (targetTypePredicate) { const sourceTypePredicate = getTypePredicateOfSignature(source); if (sourceTypePredicate) { result &= compareTypePredicateRelatedTo(sourceTypePredicate, targetTypePredicate, reportErrors, errorReporter, compareTypes); } else if (isIdentifierTypePredicate(targetTypePredicate)) { if (reportErrors) { errorReporter!(Diagnostics.Signature_0_must_be_a_type_predicate, signatureToString(source)); } return Ternary.False; } } else { // When relating callback signatures, we still need to relate return types bi-variantly as otherwise // the containing type wouldn't be co-variant. For example, interface Foo { add(cb: () => T): void } // wouldn't be co-variant for T without this rule. result &= checkMode & SignatureCheckMode.BivariantCallback && compareTypes(targetReturnType, sourceReturnType, /*reportErrors*/ false) || compareTypes(sourceReturnType, targetReturnType, reportErrors); if (!result && reportErrors && incompatibleErrorReporter) { incompatibleErrorReporter(sourceReturnType, targetReturnType); } } } return result; } function compareTypePredicateRelatedTo( source: TypePredicate, target: TypePredicate, reportErrors: boolean, errorReporter: ErrorReporter | undefined, compareTypes: (s: Type, t: Type, reportErrors?: boolean) => Ternary): Ternary { if (source.kind !== target.kind) { if (reportErrors) { errorReporter!(Diagnostics.A_this_based_type_guard_is_not_compatible_with_a_parameter_based_type_guard); errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); } return Ternary.False; } if (source.kind === TypePredicateKind.Identifier || source.kind === TypePredicateKind.AssertsIdentifier) { if (source.parameterIndex !== (target as IdentifierTypePredicate).parameterIndex) { if (reportErrors) { errorReporter!(Diagnostics.Parameter_0_is_not_in_the_same_position_as_parameter_1, source.parameterName, (target as IdentifierTypePredicate).parameterName); errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); } return Ternary.False; } } const related = source.type === target.type ? Ternary.True : source.type && target.type ? compareTypes(source.type, target.type, reportErrors) : Ternary.False; if (related === Ternary.False && reportErrors) { errorReporter!(Diagnostics.Type_predicate_0_is_not_assignable_to_1, typePredicateToString(source), typePredicateToString(target)); } return related; } function isImplementationCompatibleWithOverload(implementation: Signature, overload: Signature): boolean { const erasedSource = getErasedSignature(implementation); const erasedTarget = getErasedSignature(overload); // First see if the return types are compatible in either direction. const sourceReturnType = getReturnTypeOfSignature(erasedSource); const targetReturnType = getReturnTypeOfSignature(erasedTarget); if (targetReturnType === voidType || isTypeRelatedTo(targetReturnType, sourceReturnType, assignableRelation) || isTypeRelatedTo(sourceReturnType, targetReturnType, assignableRelation)) { return isSignatureAssignableTo(erasedSource, erasedTarget, /*ignoreReturnTypes*/ true); } return false; } function isEmptyResolvedType(t: ResolvedType) { return t !== anyFunctionType && t.properties.length === 0 && t.callSignatures.length === 0 && t.constructSignatures.length === 0 && !t.stringIndexInfo && !t.numberIndexInfo; } function isEmptyObjectType(type: Type): boolean { return type.flags & TypeFlags.Object ? !isGenericMappedType(type) && isEmptyResolvedType(resolveStructuredTypeMembers(type)) : type.flags & TypeFlags.NonPrimitive ? true : type.flags & TypeFlags.Union ? some((type).types, isEmptyObjectType) : type.flags & TypeFlags.Intersection ? every((type).types, isEmptyObjectType) : false; } function isEmptyAnonymousObjectType(type: Type) { return !!(getObjectFlags(type) & ObjectFlags.Anonymous && ( (type).members && isEmptyResolvedType(type) || type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral && getMembersOfSymbol(type.symbol).size === 0)); } function isStringIndexSignatureOnlyType(type: Type): boolean { return type.flags & TypeFlags.Object && !isGenericMappedType(type) && getPropertiesOfType(type).length === 0 && getIndexInfoOfType(type, IndexKind.String) && !getIndexInfoOfType(type, IndexKind.Number) || type.flags & TypeFlags.UnionOrIntersection && every((type).types, isStringIndexSignatureOnlyType) || false; } function isEnumTypeRelatedTo(sourceSymbol: Symbol, targetSymbol: Symbol, errorReporter?: ErrorReporter) { if (sourceSymbol === targetSymbol) { return true; } const id = getSymbolId(sourceSymbol) + "," + getSymbolId(targetSymbol); const entry = enumRelation.get(id); if (entry !== undefined && !(!(entry & RelationComparisonResult.Reported) && entry & RelationComparisonResult.Failed && errorReporter)) { return !!(entry & RelationComparisonResult.Succeeded); } if (sourceSymbol.escapedName !== targetSymbol.escapedName || !(sourceSymbol.flags & SymbolFlags.RegularEnum) || !(targetSymbol.flags & SymbolFlags.RegularEnum)) { enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); return false; } const targetEnumType = getTypeOfSymbol(targetSymbol); for (const property of getPropertiesOfType(getTypeOfSymbol(sourceSymbol))) { if (property.flags & SymbolFlags.EnumMember) { const targetProperty = getPropertyOfType(targetEnumType, property.escapedName); if (!targetProperty || !(targetProperty.flags & SymbolFlags.EnumMember)) { if (errorReporter) { errorReporter(Diagnostics.Property_0_is_missing_in_type_1, symbolName(property), typeToString(getDeclaredTypeOfSymbol(targetSymbol), /*enclosingDeclaration*/ undefined, TypeFormatFlags.UseFullyQualifiedType)); enumRelation.set(id, RelationComparisonResult.Failed | RelationComparisonResult.Reported); } else { enumRelation.set(id, RelationComparisonResult.Failed); } return false; } } } enumRelation.set(id, RelationComparisonResult.Succeeded); return true; } function isSimpleTypeRelatedTo(source: Type, target: Type, relation: ESMap, errorReporter?: ErrorReporter) { const s = source.flags; const t = target.flags; if (t & TypeFlags.AnyOrUnknown || s & TypeFlags.Never || source === wildcardType) return true; if (t & TypeFlags.Never) return false; if (s & TypeFlags.StringLike && t & TypeFlags.String) return true; if (s & TypeFlags.StringLiteral && s & TypeFlags.EnumLiteral && t & TypeFlags.StringLiteral && !(t & TypeFlags.EnumLiteral) && (source).value === (target).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).value === (target).value) return true; if (s & TypeFlags.BigIntLike && t & TypeFlags.BigInt) return true; if (s & TypeFlags.BooleanLike && t & TypeFlags.Boolean) return true; if (s & TypeFlags.ESSymbolLike && t & TypeFlags.ESSymbol) return true; if (s & TypeFlags.Enum && t & TypeFlags.Enum && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true; if (s & TypeFlags.EnumLiteral && t & TypeFlags.EnumLiteral) { if (s & TypeFlags.Union && t & TypeFlags.Union && isEnumTypeRelatedTo(source.symbol, target.symbol, errorReporter)) return true; if (s & TypeFlags.Literal && t & TypeFlags.Literal && (source).value === (target).value && isEnumTypeRelatedTo(getParentOfSymbol(source.symbol)!, getParentOfSymbol(target.symbol)!, errorReporter)) return true; } if (s & TypeFlags.Undefined && (!strictNullChecks || t & (TypeFlags.Undefined | TypeFlags.Void))) return true; if (s & TypeFlags.Null && (!strictNullChecks || t & TypeFlags.Null)) return true; if (s & TypeFlags.Object && t & TypeFlags.NonPrimitive) return true; if (relation === assignableRelation || relation === comparableRelation) { if (s & TypeFlags.Any) return true; // Type number or any numeric literal type is assignable to any numeric enum type or any // numeric enum literal type. This rule exists for backwards compatibility reasons because // bit-flag enum types sometimes look like literal enum types with numeric literal values. if (s & (TypeFlags.Number | TypeFlags.NumberLiteral) && !(s & TypeFlags.EnumLiteral) && ( t & TypeFlags.Enum || t & TypeFlags.NumberLiteral && t & TypeFlags.EnumLiteral)) return true; } return false; } function isTypeRelatedTo(source: Type, target: Type, relation: ESMap) { if (isFreshLiteralType(source)) { source = (source).regularType; } if (isFreshLiteralType(target)) { target = (target).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 & TypeFlags.UnionOrIntersection) && !(target.flags & TypeFlags.UnionOrIntersection) && source.flags !== target.flags && !(source.flags & TypeFlags.Substructure)) return false; } if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { const related = relation.get(getRelationKey(source, target, IntersectionState.None, relation)); if (related !== undefined) { return !!(related & RelationComparisonResult.Succeeded); } } if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) { return checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined); } return false; } function isIgnoredJsxProperty(source: Type, sourceProp: Symbol) { return getObjectFlags(source) & ObjectFlags.JsxAttributes && !isUnhyphenatedJsxName(sourceProp.escapedName); } function getNormalizedType(type: Type, writing: boolean): Type { while (true) { const t = isFreshLiteralType(type) ? (type).regularType : getObjectFlags(type) & ObjectFlags.Reference && (type).node ? createTypeReference((type).target, getTypeArguments(type)) : type.flags & TypeFlags.UnionOrIntersection ? getReducedType(type) : type.flags & TypeFlags.Substitution ? writing ? (type).baseType : (type).substitute : type.flags & TypeFlags.Simplifiable ? getSimplifiedType(type, writing) : type; if (t === type) break; type = t; } return type; } /** * Checks if 'source' is related to 'target' (e.g.: is a assignable to). * @param source The left-hand-side of the relation. * @param target The right-hand-side of the relation. * @param relation The relation considered. One of 'identityRelation', 'subtypeRelation', 'assignableRelation', or 'comparableRelation'. * Used as both to determine which checks are performed and as a cache of previously computed results. * @param errorNode The suggested node upon which all errors will be reported, if defined. This may or may not be the actual node used. * @param headMessage If the error chain should be prepended by a head message, then headMessage will be used. * @param containingMessageChain A chain of errors to prepend any new errors found. * @param errorOutputContainer Return the diagnostic. Do not log if 'skipLogging' is truthy. */ function checkTypeRelatedTo( source: Type, target: Type, relation: ESMap, errorNode: Node | undefined, headMessage?: DiagnosticMessage, containingMessageChain?: () => DiagnosticMessageChain | undefined, errorOutputContainer?: { errors?: Diagnostic[], skipLogging?: boolean }, ): boolean { let errorInfo: DiagnosticMessageChain | undefined; let relatedInfo: [DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined; let maybeKeys: string[]; let sourceStack: Type[]; let targetStack: Type[]; let maybeCount = 0; let depth = 0; let expandingFlags = ExpandingFlags.None; let overflow = false; let overrideNextErrorInfo = 0; // How many `reportRelationError` calls should be skipped in the elaboration pyramid let lastSkippedInfo: [Type, Type] | undefined; let incompatibleStack: [DiagnosticMessage, (string | number)?, (string | number)?, (string | number)?, (string | number)?][] = []; let inPropertyCheck = false; Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking"); const result = isRelatedTo(source, target, /*reportErrors*/ !!errorNode, headMessage); if (incompatibleStack.length) { reportIncompatibleStack(); } if (overflow) { tracing.instant(tracing.Phase.Check, "checkTypeRelatedTo_DepthLimit", { sourceId: source.id, targetId: target.id, depth }); const diag = error(errorNode || currentNode, Diagnostics.Excessive_stack_depth_comparing_types_0_and_1, typeToString(source), typeToString(target)); if (errorOutputContainer) { (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); } } else if (errorInfo) { if (containingMessageChain) { const chain = containingMessageChain(); if (chain) { concatenateDiagnosticMessageChains(chain, errorInfo); errorInfo = chain; } } let relatedInformation: DiagnosticRelatedInformation[] | undefined; // Check if we should issue an extra diagnostic to produce a quickfix for a slightly incorrect import statement if (headMessage && errorNode && !result && source.symbol) { const links = getSymbolLinks(source.symbol); if (links.originatingImport && !isImportCall(links.originatingImport)) { const helpfulRetry = checkTypeRelatedTo(getTypeOfSymbol(links.target!), target, relation, /*errorNode*/ undefined); if (helpfulRetry) { // Likely an incorrect import. Issue a helpful diagnostic to produce a quickfix to change the import const diag = createDiagnosticForNode(links.originatingImport, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead); relatedInformation = append(relatedInformation, diag); // Cause the error to appear with the error that triggered it } } } const diag = createDiagnosticForNodeFromMessageChain(errorNode!, errorInfo, relatedInformation); if (relatedInfo) { addRelatedInfo(diag, ...relatedInfo); } if (errorOutputContainer) { (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); } if (!errorOutputContainer || !errorOutputContainer.skipLogging) { diagnostics.add(diag); } } if (errorNode && errorOutputContainer && errorOutputContainer.skipLogging && result === Ternary.False) { Debug.assert(!!errorOutputContainer.errors, "missed opportunity to interact with error."); } return result !== Ternary.False; function resetErrorInfo(saved: ReturnType) { errorInfo = saved.errorInfo; lastSkippedInfo = saved.lastSkippedInfo; incompatibleStack = saved.incompatibleStack; overrideNextErrorInfo = saved.overrideNextErrorInfo; relatedInfo = saved.relatedInfo; } function captureErrorCalculationState() { return { errorInfo, lastSkippedInfo, incompatibleStack: incompatibleStack.slice(), overrideNextErrorInfo, relatedInfo: !relatedInfo ? undefined : relatedInfo.slice() as ([DiagnosticRelatedInformation, ...DiagnosticRelatedInformation[]] | undefined) }; } function reportIncompatibleError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number) { overrideNextErrorInfo++; // Suppress the next relation error lastSkippedInfo = undefined; // Reset skipped info cache incompatibleStack.push([message, arg0, arg1, arg2, arg3]); } function reportIncompatibleStack() { const stack = incompatibleStack; incompatibleStack = []; const info = lastSkippedInfo; lastSkippedInfo = undefined; if (stack.length === 1) { reportError(...stack[0]); if (info) { // Actually do the last relation error reportRelationError(/*headMessage*/ undefined, ...info); } return; } // The first error will be the innermost, while the last will be the outermost - so by popping off the end, // we can build from left to right let path = ""; const secondaryRootErrors: typeof incompatibleStack = []; while (stack.length) { const [msg, ...args] = stack.pop()!; switch (msg.code) { case Diagnostics.Types_of_property_0_are_incompatible.code: { // Parenthesize a `new` if there is one if (path.indexOf("new ") === 0) { path = `(${path})`; } const str = "" + args[0]; // If leading, just print back the arg (irrespective of if it's a valid identifier) if (path.length === 0) { path = `${str}`; } // Otherwise write a dotted name if possible else if (isIdentifierText(str, compilerOptions.target)) { 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; } 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; // Teporarily override elision to ensure error is reported reportError(msg, ...args); msg.elidedInCompatabilityPyramid = originalValue; } if (info) { // Actually do the last relation error reportRelationError(/*headMessage*/ undefined, ...info); } } function reportError(message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): void { Debug.assert(!!errorNode); if (incompatibleStack.length) reportIncompatibleStack(); if (message.elidedInCompatabilityPyramid) return; errorInfo = chainDiagnosticMessages(errorInfo, message, arg0, arg1, arg2, arg3); } function associateRelatedInfo(info: DiagnosticRelatedInformation) { Debug.assert(!!errorInfo); if (!relatedInfo) { relatedInfo = [info]; } else { relatedInfo.push(info); } } function reportRelationError(message: DiagnosticMessage | undefined, source: Type, target: Type) { if (incompatibleStack.length) reportIncompatibleStack(); const [sourceType, targetType] = getTypeNamesForErrorDisplay(source, target); let generalizedSource = source; let generalizedSourceType = sourceType; if (isLiteralType(source) && !typeCouldHaveTopLevelSingletonTypes(target)) { generalizedSource = getBaseTypeOfLiteralType(source); Debug.assert(!isTypeAssignableTo(generalizedSource, target), "generalized source shouldn't be assignable"); generalizedSourceType = getTypeNameForErrorDisplay(generalizedSource); } if (target.flags & TypeFlags.TypeParameter) { const constraint = getBaseConstraintOfType(target); let needsOriginalSource; if (constraint && (isTypeAssignableTo(generalizedSource, constraint) || (needsOriginalSource = isTypeAssignableTo(source, constraint)))) { reportError( Diagnostics._0_is_assignable_to_the_constraint_of_type_1_but_1_could_be_instantiated_with_a_different_subtype_of_constraint_2, needsOriginalSource ? sourceType : generalizedSourceType, targetType, typeToString(constraint), ); } else { 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 { message = Diagnostics.Type_0_is_not_assignable_to_type_1; } } reportError(message, generalizedSourceType, targetType); } function tryElaborateErrorsForPrimitivesAndObjects(source: Type, target: Type) { const sourceType = symbolValueDeclarationIsContextSensitive(source.symbol) ? typeToString(source, source.symbol.valueDeclaration) : typeToString(source); const targetType = symbolValueDeclarationIsContextSensitive(target.symbol) ? typeToString(target, target.symbol.valueDeclaration) : typeToString(target); if ((globalStringType === source && stringType === target) || (globalNumberType === source && numberType === target) || (globalBooleanType === source && booleanType === target) || (getGlobalESSymbolType(/*reportErrors*/ false) === source && esSymbolType === target)) { reportError(Diagnostics._0_is_a_primitive_but_1_is_a_wrapper_object_Prefer_using_0_when_possible, targetType, sourceType); } } /** * Try and elaborate array and tuple errors. Returns false * if we have found an elaboration, or we should ignore * any other elaborations when relating the `source` and * `target` types. */ function tryElaborateArrayLikeErrors(source: Type, target: Type, reportErrors: boolean): boolean { /** * The spec for elaboration is: * - If the source is a readonly tuple and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations. * - If the source is a tuple then skip property elaborations if the target is an array or tuple. * - If the source is a readonly array and the target is a mutable array or tuple, elaborate on mutability and skip property elaborations. * - If the source an array then skip property elaborations if the target is a tuple. */ if (isTupleType(source)) { if (source.target.readonly && isMutableArrayOrTuple(target)) { if (reportErrors) { reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target)); } return false; } return isTupleType(target) || isArrayType(target); } if (isReadonlyArrayType(source) && isMutableArrayOrTuple(target)) { if (reportErrors) { reportError(Diagnostics.The_type_0_is_readonly_and_cannot_be_assigned_to_the_mutable_type_1, typeToString(source), typeToString(target)); } return false; } if (isTupleType(target)) { return isArrayType(source); } return true; } /** * 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, reportErrors = false, headMessage?: DiagnosticMessage, intersectionState = IntersectionState.None): Ternary { // Before normalization: if `source` is type an object type, and `target` is primitive, // skip all the checks we don't need and just return `isSimpleTypeRelatedTo` result if (originalSource.flags & TypeFlags.Object && originalTarget.flags & TypeFlags.Primitive) { if (isSimpleTypeRelatedTo(originalSource, originalTarget, relation, reportErrors ? reportError : undefined)) { return Ternary.True; } reportErrorResults(originalSource, originalTarget, Ternary.False, !!(getObjectFlags(originalSource) & ObjectFlags.JsxAttributes)); return Ternary.False; } // Normalize the source and target types: Turn fresh literal types into regular literal types, // turn deferred type references into regular type references, simplify indexed access and // conditional types, and resolve substitution types to either the substitution (on the source // side) or the type variable (on the target side). const source = getNormalizedType(originalSource, /*writing*/ false); let target = getNormalizedType(originalTarget, /*writing*/ true); if (source === target) return Ternary.True; if (relation === identityRelation) { return isIdenticalTo(source, target); } // We fastpath comparing a type parameter to exactly its constraint, as this is _super_ common, // and otherwise, for type parameters in large unions, causes us to need to compare the union to itself, // as we break down the _target_ union first, _then_ get the source constraint - so for every // member of the target, we attempt to find a match in the source. This avoids that in cases where // the target is exactly the constraint. if (source.flags & TypeFlags.TypeParameter && getConstraintOfType(source) === target) { return Ternary.True; } // Try to see if we're relating something like `Foo` -> `Bar | null | undefined`. // If so, reporting the `null` and `undefined` in the type is hardly useful. // First, see if we're even relating an object type to a union. // Then see if the target is stripped down to a single non-union type. // Note // * We actually want to remove null and undefined naively here (rather than using getNonNullableType), // since we don't want to end up with a worse error like "`Foo` is not assignable to `NonNullable`" // when dealing with generics. // * We also don't deal with primitive source types, since we already halt elaboration below. if (target.flags & TypeFlags.Union && source.flags & TypeFlags.Object && (target as UnionType).types.length <= 3 && maybeTypeOfKind(target, TypeFlags.Nullable)) { const nullStrippedTarget = extractTypesOfKind(target, ~TypeFlags.Nullable); if (!(nullStrippedTarget.flags & (TypeFlags.Union | TypeFlags.Never))) { if (source === nullStrippedTarget) return Ternary.True; target = nullStrippedTarget; } } if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True; const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes); const isPerformingExcessPropertyChecks = !(intersectionState & IntersectionState.Target) && (isObjectLiteralType(source) && getObjectFlags(source) & ObjectFlags.FreshLiteral); if (isPerformingExcessPropertyChecks) { if (hasExcessProperties(source, target, reportErrors)) { if (reportErrors) { reportRelationError(headMessage, source, target); } return Ternary.False; } } const isPerformingCommonPropertyChecks = relation !== comparableRelation && !(intersectionState & IntersectionState.Target) && source.flags & (TypeFlags.Primitive | TypeFlags.Object | TypeFlags.Intersection) && source !== globalObjectType && target.flags & (TypeFlags.Object | TypeFlags.Intersection) && isWeakType(target) && (getPropertiesOfType(source).length > 0 || typeHasCallOrConstructSignatures(source)); if (isPerformingCommonPropertyChecks && !hasCommonProperties(source, target, isComparingJsxAttributes)) { if (reportErrors) { const calls = getSignaturesOfType(source, SignatureKind.Call); const constructs = getSignaturesOfType(source, SignatureKind.Construct); if (calls.length > 0 && isRelatedTo(getReturnTypeOfSignature(calls[0]), target, /*reportErrors*/ false) || constructs.length > 0 && isRelatedTo(getReturnTypeOfSignature(constructs[0]), target, /*reportErrors*/ false)) { reportError(Diagnostics.Value_of_type_0_has_no_properties_in_common_with_type_1_Did_you_mean_to_call_it, typeToString(source), typeToString(target)); } else { reportError(Diagnostics.Type_0_has_no_properties_in_common_with_type_1, typeToString(source), typeToString(target)); } } return Ternary.False; } let result = Ternary.False; const saveErrorInfo = captureErrorCalculationState(); // Note that these checks are specifically ordered to produce correct results. In particular, // we need to deconstruct unions before intersections (because unions are always at the top), // and we need to handle "each" relations before "some" relations for the same kind of type. if (source.flags & TypeFlags.Union) { result = relation === comparableRelation ? someTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState) : eachTypeRelatedToType(source as UnionType, target, reportErrors && !(source.flags & TypeFlags.Primitive), intersectionState); } else { if (target.flags & TypeFlags.Union) { result = typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive)); } else if (target.flags & TypeFlags.Intersection) { result = typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors, IntersectionState.Target); } else if (source.flags & TypeFlags.Intersection) { // Check to see if any constituents of the intersection are immediately related to the target. // // Don't report errors though. Checking whether a constituent is related to the source is not actually // useful and leads to some confusing error messages. Instead it is better to let the below checks // take care of this, or to not elaborate at all. For instance, // // - For an object type (such as 'C = A & B'), users are usually more interested in structural errors. // // - For a union type (such as '(A | B) = (C & D)'), it's better to hold onto the whole intersection // than to report that 'D' is not assignable to 'A' or 'B'. // // - For a primitive type or type parameter (such as 'number = A & B') there is no point in // breaking the intersection apart. result = someTypeRelatedToType(source, target, /*reportErrors*/ false, IntersectionState.Source); } if (!result && (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable)) { if (result = recursiveTypeRelatedTo(source, target, reportErrors, intersectionState)) { resetErrorInfo(saveErrorInfo); } } } if (!result && source.flags & (TypeFlags.Intersection | TypeFlags.TypeParameter)) { // The combined constraint of an intersection type is the intersection of the constraints of // the constituents. When an intersection type contains instantiable types with union type // constraints, there are situations where we need to examine the combined constraint. One is // when the target is a union type. Another is when the intersection contains types belonging // to one of the disjoint domains. For example, given type variables T and U, each with the // constraint 'string | number', the combined constraint of 'T & U' is 'string | number' and // we need to check this constraint against a union on the target side. Also, given a type // variable V constrained to 'string | number', 'V & number' has a combined constraint of // 'string & number | number & number' which reduces to just 'number'. // This also handles type parameters, as a type parameter with a union constraint compared against a union // needs to have its constraint hoisted into an intersection with said type parameter, this way // the type param can be compared with itself in the target (with the influence of its constraint to match other parts) // For example, if `T extends 1 | 2` and `U extends 2 | 3` and we compare `T & U` to `T & U & (1 | 2 | 3)` const constraint = getEffectiveConstraintOfIntersection(source.flags & TypeFlags.Intersection ? (source).types: [source], !!(target.flags & TypeFlags.Union)); if (constraint && (source.flags & TypeFlags.Intersection || target.flags & TypeFlags.Union)) { if (everyType(constraint, c => c !== source)) { // Skip comparison if expansion contains the source itself // TODO: Stack errors so we get a pyramid for the "normal" comparison above, _and_ a second for this if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { resetErrorInfo(saveErrorInfo); } } } } // For certain combinations involving intersections and optional, excess, or mismatched properties we need // an extra property check where the intersection is viewed as a single object. The following are motivating // examples that all should be errors, but aren't without this extra property check: // // let obj: { a: { x: string } } & { c: number } = { a: { x: 'hello', y: 2 }, c: 5 }; // Nested excess property // // declare let wrong: { a: { y: string } }; // let weak: { a?: { x?: number } } & { c?: string } = wrong; // Nested weak object type // // function foo(x: { a?: string }, y: T & { a: boolean }) { // x = y; // Mismatched property in source intersection // } // // We suppress recursive intersection property checks because they can generate lots of work when relating // recursive intersections that are structurally similar but not exactly identical. See #37854. if (result && !inPropertyCheck && ( target.flags & TypeFlags.Intersection && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks) || isNonGenericObjectType(target) && !isArrayType(target) && !isTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) { inPropertyCheck = true; result &= recursiveTypeRelatedTo(source, target, reportErrors, IntersectionState.PropertyCheck); inPropertyCheck = false; } reportErrorResults(source, target, result, isComparingJsxAttributes); return result; function reportErrorResults(source: Type, target: Type, result: Ternary, isComparingJsxAttributes: boolean) { if (!result && reportErrors) { source = originalSource.aliasSymbol ? originalSource : source; target = originalTarget.aliasSymbol ? originalTarget : target; let maybeSuppress = overrideNextErrorInfo > 0; if (maybeSuppress) { overrideNextErrorInfo--; } if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { const currentError = errorInfo; tryElaborateArrayLikeErrors(source, target, reportErrors); if (errorInfo !== currentError) { maybeSuppress = !!errorInfo; } } if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Primitive) { tryElaborateErrorsForPrimitivesAndObjects(source, target); } else if (source.symbol && source.flags & TypeFlags.Object && globalObjectType === source) { reportError(Diagnostics.The_Object_type_is_assignable_to_very_few_other_types_Did_you_mean_to_use_the_any_type_instead); } else if (isComparingJsxAttributes && target.flags & TypeFlags.Intersection) { const targetTypes = (target as IntersectionType).types; const intrinsicAttributes = getJsxType(JsxNames.IntrinsicAttributes, errorNode); const intrinsicClassAttributes = getJsxType(JsxNames.IntrinsicClassAttributes, errorNode); if (intrinsicAttributes !== errorType && intrinsicClassAttributes !== errorType && (contains(targetTypes, intrinsicAttributes) || contains(targetTypes, intrinsicClassAttributes))) { // do not report top error return result; } } else { errorInfo = elaborateNeverIntersection(errorInfo, originalTarget); } if (!headMessage && maybeSuppress) { lastSkippedInfo = [source, target]; // Used by, eg, missing property checking to replace the top-level message with a more informative one return result; } reportRelationError(headMessage, source, target); } } } function isIdenticalTo(source: Type, target: Type): Ternary { const flags = source.flags & target.flags; if (!(flags & TypeFlags.Substructure)) { return Ternary.False; } if (flags & TypeFlags.UnionOrIntersection) { let result = eachTypeRelatedToSomeType(source, target); if (result) { result &= eachTypeRelatedToSomeType(target, source); } return result; } return recursiveTypeRelatedTo(source, target, /*reportErrors*/ false, IntersectionState.None); } function getTypeOfPropertyInTypes(types: Type[], name: __String) { const appendPropType = (propTypes: Type[] | undefined, type: Type) => { type = getApparentType(type); const prop = type.flags & TypeFlags.UnionOrIntersection ? getPropertyOfUnionOrIntersectionType(type, name) : getPropertyOfObjectType(type, name); const propType = prop && getTypeOfSymbol(prop) || isNumericLiteralName(name) && getIndexTypeOfType(type, IndexKind.Number) || getIndexTypeOfType(type, IndexKind.String) || 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, isRelatedTo) || filterPrimitivesIfContainsNonPrimitive(target); checkTypes = reducedTarget.flags & TypeFlags.Union ? (reducedTarget).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 && firstOrUndefined(source.symbol.declarations); let suggestion: string | undefined; if (prop.valueDeclaration && findAncestor(prop.valueDeclaration, d => d === objectLiteralDeclaration) && getSourceFileOfNode(objectLiteralDeclaration) === getSourceFileOfNode(errorNode)) { const propDeclaration = prop.valueDeclaration as ObjectLiteralElementLike; Debug.assertNode(propDeclaration, isObjectLiteralElementLike); errorNode = propDeclaration; const name = propDeclaration.name!; if (isIdentifier(name)) { suggestion = getSuggestionForNonexistentProperty(name, errorTarget); } } if (suggestion !== undefined) { reportError(Diagnostics.Object_literal_may_only_specify_known_properties_but_0_does_not_exist_in_type_1_Did_you_mean_to_write_2, symbolToString(prop), typeToString(errorTarget), suggestion); } else { reportError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, symbolToString(prop), typeToString(errorTarget)); } } } return true; } if (checkTypes && !isRelatedTo(getTypeOfSymbol(prop), getTypeOfPropertyInTypes(checkTypes, prop.escapedName), reportErrors)) { if (reportErrors) { reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(prop)); } return true; } } } return false; } function shouldCheckAsExcessProperty(prop: Symbol, container: Symbol) { return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent === container.valueDeclaration; } function eachTypeRelatedToSomeType(source: UnionOrIntersectionType, target: UnionOrIntersectionType): Ternary { let result = Ternary.True; const sourceTypes = source.types; for (const sourceType of sourceTypes) { const related = typeRelatedToSomeType(sourceType, target, /*reportErrors*/ false); if (!related) { return Ternary.False; } result &= related; } return result; } function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary { const targetTypes = target.types; if (target.flags & TypeFlags.Union && containsType(targetTypes, source)) { return Ternary.True; } for (const type of targetTypes) { const related = isRelatedTo(source, type, /*reportErrors*/ false); if (related) { return related; } } if (reportErrors) { const bestMatchingType = getBestMatchingType(source, target, isRelatedTo); isRelatedTo(source, bestMatchingType || targetTypes[targetTypes.length - 1], /*reportErrors*/ true); } return Ternary.False; } function typeRelatedToEachType(source: Type, target: IntersectionType, reportErrors: boolean, intersectionState: IntersectionState): Ternary { let result = Ternary.True; const targetTypes = target.types; for (const targetType of targetTypes) { const related = isRelatedTo(source, targetType, reportErrors, /*headMessage*/ undefined, intersectionState); if (!related) { return Ternary.False; } result &= related; } return result; } function someTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { const sourceTypes = source.types; if (source.flags & TypeFlags.Union && containsType(sourceTypes, target)) { return Ternary.True; } const len = sourceTypes.length; for (let i = 0; i < len; i++) { const related = isRelatedTo(sourceTypes[i], target, reportErrors && i === len - 1, /*headMessage*/ undefined, intersectionState); if (related) { return related; } } return Ternary.False; } function eachTypeRelatedToType(source: UnionOrIntersectionType, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { let result = Ternary.True; const sourceTypes = source.types; for (let i = 0; i < sourceTypes.length; i++) { const sourceType = sourceTypes[i]; if (target.flags & TypeFlags.Union && (target as UnionType).types.length === sourceTypes.length) { // many unions are mappings of one another; in such cases, simply comparing members at the same index can shortcut the comparison const related = isRelatedTo(sourceType, (target as UnionType).types[i], /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState); if (related) { result &= related; continue; } } const related = isRelatedTo(sourceType, target, reportErrors, /*headMessage*/ undefined, intersectionState); if (!related) { return Ternary.False; } result &= related; } return result; } function typeArgumentsRelatedTo(sources: readonly Type[] = emptyArray, targets: readonly Type[] = emptyArray, variances: readonly VarianceFlags[] = emptyArray, reportErrors: boolean, intersectionState: IntersectionState): Ternary { if (sources.length !== targets.length && relation === identityRelation) { return Ternary.False; } const length = sources.length <= targets.length ? sources.length : targets.length; let result = Ternary.True; for (let i = 0; i < length; i++) { // When variance information isn't available we default to covariance. This happens // in the process of computing variance information for recursive types and when // comparing 'this' type arguments. const varianceFlags = i < variances.length ? variances[i] : VarianceFlags.Covariant; const variance = varianceFlags & VarianceFlags.VarianceMask; // We ignore arguments for independent type parameters (because they're never witnessed). if (variance !== VarianceFlags.Independent) { const s = sources[i]; const t = targets[i]; let related = Ternary.True; if (varianceFlags & VarianceFlags.Unmeasurable) { // Even an `Unmeasurable` variance works out without a structural check if the source and target are _identical_. // We can't simply assume invariance, because `Unmeasurable` marks nonlinear relations, for example, a relation tained by // the `-?` modifier in a mapped type (where, no matter how the inputs are related, the outputs still might not be) related = relation === identityRelation ? isRelatedTo(s, t, /*reportErrors*/ false) : compareTypesIdentical(s, t); } else if (variance === VarianceFlags.Covariant) { related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, intersectionState); } else if (variance === VarianceFlags.Contravariant) { related = isRelatedTo(t, s, reportErrors, /*headMessage*/ undefined, intersectionState); } else if (variance === VarianceFlags.Bivariant) { // In the bivariant case we first compare contravariantly without reporting // errors. Then, if that doesn't succeed, we compare covariantly with error // reporting. Thus, error elaboration will be based on the the covariant check, // which is generally easier to reason about. related = isRelatedTo(t, s, /*reportErrors*/ false); if (!related) { related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, intersectionState); } } else { // In the invariant case we first compare covariantly, and only when that // succeeds do we proceed to compare contravariantly. Thus, error elaboration // will typically be based on the covariant check. related = isRelatedTo(s, t, reportErrors, /*headMessage*/ undefined, intersectionState); if (related) { related &= isRelatedTo(t, s, reportErrors, /*headMessage*/ undefined, intersectionState); } } if (!related) { return Ternary.False; } result &= related; } } return result; } // Determine if possibly recursive types are related. First, check if the result is already available in the global cache. // Second, check if we have already started a comparison of the given two types in which case we assume the result to be true. // Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are // equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion // and issue an error. Otherwise, actually compare the structure of the two types. function recursiveTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { if (overflow) { return Ternary.False; } const id = getRelationKey(source, target, intersectionState | (inPropertyCheck ? IntersectionState.InPropertyCheck : 0), relation); const entry = relation.get(id); if (entry !== undefined) { if (reportErrors && entry & RelationComparisonResult.Failed && !(entry & RelationComparisonResult.Reported)) { // We are elaborating errors and the cached result is an unreported failure. The result will be reported // as a failure, and should be updated as a reported failure by the bottom of this function. } else { if (outofbandVarianceMarkerHandler) { // We're in the middle of variance checking - integrate any unmeasurable/unreliable flags from this cached component const saved = entry & RelationComparisonResult.ReportsMask; if (saved & RelationComparisonResult.ReportsUnmeasurable) { instantiateType(source, makeFunctionTypeMapper(reportUnmeasurableMarkers)); } if (saved & RelationComparisonResult.ReportsUnreliable) { instantiateType(source, makeFunctionTypeMapper(reportUnreliableMarkers)); } } return entry & RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False; } } if (!maybeKeys) { maybeKeys = []; sourceStack = []; targetStack = []; } else { for (let i = 0; i < maybeCount; i++) { // If source and target are already being compared, consider them related with assumptions if (id === maybeKeys[i]) { return Ternary.Maybe; } } if (depth === 100) { overflow = true; return Ternary.False; } } const maybeStart = maybeCount; maybeKeys[maybeCount] = id; maybeCount++; sourceStack[depth] = source; targetStack[depth] = target; depth++; const saveExpandingFlags = expandingFlags; if (!(expandingFlags & ExpandingFlags.Source) && isDeeplyNestedType(source, sourceStack, depth)) expandingFlags |= ExpandingFlags.Source; if (!(expandingFlags & ExpandingFlags.Target) && isDeeplyNestedType(target, targetStack, depth)) expandingFlags |= ExpandingFlags.Target; let originalHandler: typeof outofbandVarianceMarkerHandler; let propagatingVarianceFlags: RelationComparisonResult = 0; if (outofbandVarianceMarkerHandler) { originalHandler = outofbandVarianceMarkerHandler; outofbandVarianceMarkerHandler = onlyUnreliable => { propagatingVarianceFlags |= onlyUnreliable ? RelationComparisonResult.ReportsUnreliable : RelationComparisonResult.ReportsUnmeasurable; return originalHandler!(onlyUnreliable); }; } if (expandingFlags === ExpandingFlags.Both) { tracing.instant(tracing.Phase.Check, "recursiveTypeRelatedTo_DepthLimit", { sourceId: source.id, sourceIdStack: sourceStack.map(t => t.id), targetId: target.id, targetIdStack: targetStack.map(t => t.id), depth, }); } const result = expandingFlags !== ExpandingFlags.Both ? structuredTypeRelatedTo(source, target, reportErrors, intersectionState) : Ternary.Maybe; if (outofbandVarianceMarkerHandler) { outofbandVarianceMarkerHandler = originalHandler; } expandingFlags = saveExpandingFlags; depth--; if (result) { if (result === Ternary.True || depth === 0) { if (result === Ternary.True || result === Ternary.Maybe) { // If result is definitely true, record all maybe keys as having succeeded. Also, record Ternary.Maybe // results as having succeeded once we reach depth 0, but never record Ternary.Unknown results. for (let i = maybeStart; i < maybeCount; i++) { relation.set(maybeKeys[i], RelationComparisonResult.Succeeded | propagatingVarianceFlags); } } maybeCount = maybeStart; } } else { // A false result goes straight into global cache (when something is false under // assumptions it will also be false without assumptions) relation.set(id, (reportErrors ? RelationComparisonResult.Reported : 0) | RelationComparisonResult.Failed | propagatingVarianceFlags); maybeCount = maybeStart; } return result; } function structuredTypeRelatedTo(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { tracing.push(tracing.Phase.Check, "structuredTypeRelatedTo", { sourceId: source.id, targetId: target.id }); const result = structuredTypeRelatedToWorker(source, target, reportErrors, intersectionState); tracing.pop(); return result; } function structuredTypeRelatedToWorker(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { if (intersectionState & IntersectionState.PropertyCheck) { return propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None); } const flags = source.flags & target.flags; if (relation === identityRelation && !(flags & TypeFlags.Object)) { if (flags & TypeFlags.Index) { return isRelatedTo((source).type, (target).type, /*reportErrors*/ false); } let result = Ternary.False; if (flags & TypeFlags.IndexedAccess) { if (result = isRelatedTo((source).objectType, (target).objectType, /*reportErrors*/ false)) { if (result &= isRelatedTo((source).indexType, (target).indexType, /*reportErrors*/ false)) { return result; } } } if (flags & TypeFlags.Conditional) { if ((source).root.isDistributive === (target).root.isDistributive) { if (result = isRelatedTo((source).checkType, (target).checkType, /*reportErrors*/ false)) { if (result &= isRelatedTo((source).extendsType, (target).extendsType, /*reportErrors*/ false)) { if (result &= isRelatedTo(getTrueTypeFromConditionalType(source), getTrueTypeFromConditionalType(target), /*reportErrors*/ false)) { if (result &= isRelatedTo(getFalseTypeFromConditionalType(source), getFalseTypeFromConditionalType(target), /*reportErrors*/ false)) { return result; } } } } } } if (flags & TypeFlags.Substitution) { return isRelatedTo((source).substitute, (target).substitute, /*reportErrors*/ false); } return Ternary.False; } let result: Ternary; let originalErrorInfo: DiagnosticMessageChain | undefined; let varianceCheckFailed = false; const saveErrorInfo = captureErrorCalculationState(); // We limit alias variance probing to only object and conditional types since their alias behavior // is more predictable than other, interned types, which may or may not have an alias depending on // the order in which things were checked. if (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol && !(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) { const variances = getAliasVariances(source.aliasSymbol); if (variances === emptyArray) { return Ternary.Unknown; } const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, intersectionState); if (varianceResult !== undefined) { return varianceResult; } } // For a generic type T and a type U that is assignable to T, [...U] is assignable to T, U is assignable to readonly [...T], // and U is assignable to [...T] when U is constrained to a mutable array or tuple type. if (isSingleElementGenericTupleType(source) && !source.target.readonly && (result = isRelatedTo(getTypeArguments(source)[0], target)) || isSingleElementGenericTupleType(target) && (target.target.readonly || isMutableArrayOrTuple(getBaseConstraintOfType(source) || source)) && (result = isRelatedTo(source, getTypeArguments(target)[0]))) { return result; } if (target.flags & TypeFlags.TypeParameter) { // A source type { [P in Q]: X } is related to a target type T if keyof T is related to Q and X is related to T[Q]. if (getObjectFlags(source) & ObjectFlags.Mapped && !(source).declaration.nameType && isRelatedTo(getIndexType(target), getConstraintTypeFromMappedType(source))) { if (!(getMappedTypeModifiers(source) & MappedTypeModifiers.IncludeOptional)) { const templateType = getTemplateTypeFromMappedType(source); const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(source)); if (result = isRelatedTo(templateType, indexedAccessType, reportErrors)) { return result; } } } } else if (target.flags & TypeFlags.Index) { const targetType = (target as IndexType).type; // A keyof S is related to a keyof T if T is related to S. if (source.flags & TypeFlags.Index) { if (result = isRelatedTo(targetType, (source).type, /*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), reportErrors)) { return result; } } else { // A type S is assignable to keyof T if S is assignable to keyof C, where C is the // simplified form of T or, if T doesn't simplify, the constraint of T. const constraint = getSimplifiedTypeOrConstraint(targetType); if (constraint) { // We require Ternary.True here such that circular constraints don't cause // false positives. For example, given 'T extends { [K in keyof T]: string }', // 'keyof T' has itself as its constraint and produces a Ternary.Maybe when // related to other types. if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors) === Ternary.True) { return Ternary.True; } } } } else if (target.flags & TypeFlags.IndexedAccess) { // 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).objectType; const indexType = (target).indexType; const baseObjectType = getBaseConstraintOfType(objectType) || objectType; const baseIndexType = getBaseConstraintOfType(indexType) || indexType; if (!isGenericObjectType(baseObjectType) && !isGenericIndexType(baseIndexType)) { const accessFlags = AccessFlags.Writing | (baseObjectType !== objectType ? AccessFlags.NoIndexSignatures : 0); const constraint = getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, (target).noUncheckedIndexedAccessCandidate, /*accessNode*/ undefined, accessFlags); if (constraint && (result = isRelatedTo(source, constraint, reportErrors))) { return result; } } } } else if (isGenericMappedType(target) && !target.declaration.nameType) { // A source type T is related to a target type { [P in X]: T[P] } const template = getTemplateTypeFromMappedType(target); const modifiers = getMappedTypeModifiers(target); if (!(modifiers & MappedTypeModifiers.ExcludeOptional)) { if (template.flags & TypeFlags.IndexedAccess && (template).objectType === source && (template).indexType === getTypeParameterFromMappedType(target)) { return Ternary.True; } if (!isGenericMappedType(source)) { const targetConstraint = getConstraintTypeFromMappedType(target); const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true); const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional; const filteredByApplicability = includeOptional ? intersectTypes(targetConstraint, sourceKeys) : undefined; // A source type T is related to a target type { [P in Q]: X } if Q is related to keyof T and T[Q] is related to X. // A source type T is related to a target type { [P in Q]?: X } if some constituent Q' of Q is related to keyof T and T[Q'] is related to X. if (includeOptional ? !(filteredByApplicability!.flags & TypeFlags.Never) : isRelatedTo(targetConstraint, sourceKeys)) { const templateType = getTemplateTypeFromMappedType(target); const typeParameter = getTypeParameterFromMappedType(target); // Fastpath: When the template has the form Obj[P] where P is the mapped type parameter, directly compare `source` with `Obj` // to avoid creating the (potentially very large) number of new intermediate types made by manufacturing `source[P]` const nonNullComponent = extractTypesOfKind(templateType, ~TypeFlags.Nullable); if (nonNullComponent.flags & TypeFlags.IndexedAccess && (nonNullComponent as IndexedAccessType).indexType === typeParameter) { if (result = isRelatedTo(source, (nonNullComponent as IndexedAccessType).objectType, reportErrors)) { return result; } } else { const indexingType = filteredByApplicability ? getIntersectionType([filteredByApplicability, typeParameter]) : typeParameter; const indexedAccessType = getIndexedAccessType(source, indexingType); if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) { return result; } } } originalErrorInfo = errorInfo; resetErrorInfo(saveErrorInfo); } } } else if (target.flags & TypeFlags.TemplateLiteral && source.flags & TypeFlags.StringLiteral) { if (isPatternLiteralType(target)) { // match all non-`string` segments const result = inferLiteralsFromTemplateLiteralType(source as StringLiteralType, target as TemplateLiteralType); if (result && every(result, (r, i) => isStringLiteralTypeValueParsableAsType(r, (target as TemplateLiteralType).types[i]))) { return Ternary.True; } } } if (source.flags & TypeFlags.TypeVariable) { if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) { // 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).objectType, (target).objectType, reportErrors)) { result &= isRelatedTo((source).indexType, (target).indexType, reportErrors); } if (result) { resetErrorInfo(saveErrorInfo); return result; } } else { const constraint = getConstraintOfType(source); if (!constraint || (source.flags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) { // A type variable with no constraint is not related to the non-primitive object type. if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive))) { resetErrorInfo(saveErrorInfo); return result; } } // hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed else if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, intersectionState)) { resetErrorInfo(saveErrorInfo); return result; } // slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, reportErrors, /*headMessage*/ undefined, intersectionState)) { resetErrorInfo(saveErrorInfo); return result; } } } else if (source.flags & TypeFlags.Index) { if (result = isRelatedTo(keyofConstraintType, target, reportErrors)) { resetErrorInfo(saveErrorInfo); return result; } } else if (source.flags & TypeFlags.TemplateLiteral) { if (target.flags & TypeFlags.TemplateLiteral && (source as TemplateLiteralType).texts.length === (target as TemplateLiteralType).texts.length && (source as TemplateLiteralType).types.length === (target as TemplateLiteralType).types.length && every((source as TemplateLiteralType).texts, (t, i) => t === (target as TemplateLiteralType).texts[i]) && every((instantiateType(source, makeFunctionTypeMapper(reportUnreliableMarkers)) as TemplateLiteralType).types, (t, i) => !!((target as TemplateLiteralType).types[i].flags & (TypeFlags.Any | TypeFlags.String)) || !!isRelatedTo(t, (target as TemplateLiteralType).types[i], /*reportErrors*/ false))) { return Ternary.True; } const constraint = getBaseConstraintOfType(source); if (constraint && constraint !== source && (result = isRelatedTo(constraint, target, reportErrors))) { resetErrorInfo(saveErrorInfo); return result; } } else if (source.flags & TypeFlags.StringMapping) { if (target.flags & TypeFlags.StringMapping && (source).symbol === (target).symbol) { if (result = isRelatedTo((source).type, (target).type, reportErrors)) { resetErrorInfo(saveErrorInfo); return result; } } else { const constraint = getBaseConstraintOfType(source); if (constraint && (result = isRelatedTo(constraint, target, reportErrors))) { resetErrorInfo(saveErrorInfo); return result; } } } else if (source.flags & TypeFlags.Conditional) { if (target.flags & TypeFlags.Conditional) { // Two conditional types 'T1 extends U1 ? X1 : Y1' and 'T2 extends U2 ? X2 : Y2' are related if // one of T1 and T2 is related to the other, U1 and U2 are identical types, X1 is related to X2, // and Y1 is related to Y2. const sourceParams = (source as ConditionalType).root.inferTypeParameters; let sourceExtends = (source).extendsType; let mapper: TypeMapper | undefined; if (sourceParams) { // If the source has infer type parameters, we instantiate them in the context of the target const ctx = createInferenceContext(sourceParams, /*signature*/ undefined, InferenceFlags.None, isRelatedTo); inferTypes(ctx.inferences, (target).extendsType, sourceExtends, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); sourceExtends = instantiateType(sourceExtends, ctx.mapper); mapper = ctx.mapper; } if (isTypeIdenticalTo(sourceExtends, (target).extendsType) && (isRelatedTo((source).checkType, (target).checkType) || isRelatedTo((target).checkType, (source).checkType))) { if (result = isRelatedTo(instantiateType(getTrueTypeFromConditionalType(source), mapper), getTrueTypeFromConditionalType(target), reportErrors)) { result &= isRelatedTo(getFalseTypeFromConditionalType(source), getFalseTypeFromConditionalType(target), reportErrors); } if (result) { resetErrorInfo(saveErrorInfo); return result; } } } else { // conditionals aren't related to one another via distributive constraint as it is much too inaccurate and allows way // more assignments than are desirable (since it maps the source check type to its constraint, it loses information) const distributiveConstraint = getConstraintOfDistributiveConditionalType(source); if (distributiveConstraint) { if (result = isRelatedTo(distributiveConstraint, target, reportErrors)) { resetErrorInfo(saveErrorInfo); return result; } } } // conditionals _can_ be related to one another via normal constraint, as, eg, `A extends B ? O : never` should be assignable to `O` // when `O` is a conditional (`never` is trivially aissgnable to `O`, as is `O`!). const defaultConstraint = getDefaultConstraintOfConditionalType(source); if (defaultConstraint) { if (result = isRelatedTo(defaultConstraint, target, reportErrors)) { resetErrorInfo(saveErrorInfo); return result; } } } else { // An empty object type is related to any mapped type that includes a '?' modifier. if (relation !== subtypeRelation && relation !== strictSubtypeRelation && isPartialMappedType(target) && isEmptyObjectType(source)) { return Ternary.True; } if (isGenericMappedType(target)) { if (isGenericMappedType(source)) { if (result = mappedTypeRelatedTo(source, target, reportErrors)) { resetErrorInfo(saveErrorInfo); return result; } } return Ternary.False; } const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive); if (relation !== identityRelation) { source = getApparentType(source); } else if (isGenericMappedType(source)) { return Ternary.False; } if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target && !(getObjectFlags(source) & ObjectFlags.MarkerType || getObjectFlags(target) & ObjectFlags.MarkerType)) { // We have type references to the same generic type, and the type references are not marker // type references (which are intended by be compared structurally). Obtain the variance // information for the type parameters and relate the type arguments accordingly. const variances = getVariances((source).target); // We return Ternary.Maybe for a recursive invocation of getVariances (signalled by emptyArray). This // effectively means we measure variance only from type parameter occurrences that aren't nested in // recursive instantiations of the generic type. if (variances === emptyArray) { return Ternary.Unknown; } const varianceResult = relateVariances(getTypeArguments(source), getTypeArguments(target), variances, intersectionState); if (varianceResult !== undefined) { return varianceResult; } } else if (isReadonlyArrayType(target) ? isArrayType(source) || isTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) { if (relation !== identityRelation) { return isRelatedTo(getIndexTypeOfType(source, IndexKind.Number) || anyType, getIndexTypeOfType(target, IndexKind.Number) || anyType, reportErrors); } else { // By flags alone, we know that the `target` is a readonly array while the source is a normal array or tuple // or `target` is an array and source is a tuple - in both cases the types cannot be identical, by construction return Ternary.False; } } // Consider a fresh empty object literal type "closed" under the subtype relationship - this way `{} <- {[idx: string]: any} <- fresh({})` // and not `{} <- fresh({}) <- {[idx: string]: any}` else if ((relation === subtypeRelation || relation === strictSubtypeRelation) && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) { return Ternary.False; } // Even if relationship doesn't hold for unions, intersections, or generic type references, // it may hold in a structural comparison. // In a check of the form X = A & B, we will have previously checked if A relates to X or B relates // to X. Failing both of those we want to check if the aggregation of A and B's members structurally // relates to X. Thus, we include intersection types on the source side here. if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) { // Report structural errors only if we haven't reported any errors yet const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo.errorInfo && !sourceIsPrimitive; result = propertiesRelatedTo(source, target, reportStructuralErrors, /*excludedProperties*/ undefined, intersectionState); if (result) { result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportStructuralErrors); if (result) { result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportStructuralErrors); if (result) { result &= indexTypesRelatedTo(source, target, IndexKind.String, sourceIsPrimitive, reportStructuralErrors, intersectionState); if (result) { result &= indexTypesRelatedTo(source, target, IndexKind.Number, sourceIsPrimitive, reportStructuralErrors, intersectionState); } } } } if (varianceCheckFailed && result) { errorInfo = originalErrorInfo || errorInfo || saveErrorInfo.errorInfo; // Use variance error (there is no structural one) and return false } else if (result) { return result; } } // If S is an object type and T is a discriminated union, S may be related to T if // there exists a constituent of T for every combination of the discriminants of S // with respect to T. We do not report errors here, as we will use the existing // error result from checking each constituent of the union. if (source.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Union) { const objectOnlyTarget = extractTypesOfKind(target, TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Substitution); if (objectOnlyTarget.flags & TypeFlags.Union) { const result = typeRelatedToDiscriminatedType(source, objectOnlyTarget as UnionType); if (result) { return result; } } } } return Ternary.False; function relateVariances(sourceTypeArguments: readonly Type[] | undefined, targetTypeArguments: readonly Type[] | undefined, variances: VarianceFlags[], intersectionState: IntersectionState) { if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors, intersectionState)) { return result; } if (some(variances, v => !!(v & VarianceFlags.AllowsStructuralFallback))) { // If some type parameter was `Unmeasurable` or `Unreliable`, and we couldn't pass by assuming it was identical, then we // have to allow a structural fallback check // We elide the variance-based error elaborations, since those might not be too helpful, since we'll potentially // be assuming identity of the type parameter. originalErrorInfo = undefined; resetErrorInfo(saveErrorInfo); return undefined; } const allowStructuralFallback = targetTypeArguments && hasCovariantVoidArgument(targetTypeArguments, variances); varianceCheckFailed = !allowStructuralFallback; // The type arguments did not relate appropriately, but it may be because we have no variance // information (in which case typeArgumentsRelatedTo defaulted to covariance for all type // arguments). It might also be the case that the target type has a 'void' type argument for // a covariant type parameter that is only used in return positions within the generic type // (in which case any type argument is permitted on the source side). In those cases we proceed // with a structural comparison. Otherwise, we know for certain the instantiations aren't // related and we can return here. if (variances !== emptyArray && !allowStructuralFallback) { // In some cases generic types that are covariant in regular type checking mode become // invariant in --strictFunctionTypes mode because one or more type parameters are used in // both co- and contravariant positions. In order to make it easier to diagnose *why* such // types are invariant, if any of the type parameters are invariant we reset the reported // errors and instead force a structural comparison (which will include elaborations that // reveal the reason). // We can switch on `reportErrors` here, since varianceCheckFailed guarantees we return `False`, // we can return `False` early here to skip calculating the structural error message we don't need. if (varianceCheckFailed && !(reportErrors && some(variances, v => (v & VarianceFlags.VarianceMask) === VarianceFlags.Invariant))) { return Ternary.False; } // We remember the original error information so we can restore it in case the structural // comparison unexpectedly succeeds. This can happen when the structural comparison result // is a Ternary.Maybe for example caused by the recursion depth limiter. originalErrorInfo = errorInfo; resetErrorInfo(saveErrorInfo); } } } function reportUnmeasurableMarkers(p: TypeParameter) { if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) { outofbandVarianceMarkerHandler(/*onlyUnreliable*/ false); } return p; } function reportUnreliableMarkers(p: TypeParameter) { if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) { outofbandVarianceMarkerHandler(/*onlyUnreliable*/ true); } return p; } // A type [P in S]: X is related to a type [Q in T]: Y if T is related to S and X' is // related to Y, where X' is an instantiation of X in which P is replaced with Q. Notice // that S and T are contra-variant whereas X and Y are co-variant. function mappedTypeRelatedTo(source: MappedType, target: MappedType, reportErrors: boolean): Ternary { const modifiersRelated = relation === comparableRelation || (relation === identityRelation ? getMappedTypeModifiers(source) === getMappedTypeModifiers(target) : getCombinedMappedTypeOptionality(source) <= getCombinedMappedTypeOptionality(target)); if (modifiersRelated) { let result: Ternary; const targetConstraint = getConstraintTypeFromMappedType(target); const sourceConstraint = instantiateType(getConstraintTypeFromMappedType(source), makeFunctionTypeMapper(getCombinedMappedTypeOptionality(source) < 0 ? reportUnmeasurableMarkers : reportUnreliableMarkers)); if (result = isRelatedTo(targetConstraint, sourceConstraint, reportErrors)) { const mapper = createTypeMapper([getTypeParameterFromMappedType(source)], [getTypeParameterFromMappedType(target)]); if (instantiateType(getNameTypeFromMappedType(source), mapper) === instantiateType(getNameTypeFromMappedType(target), mapper)) { return result & isRelatedTo(instantiateType(getTemplateTypeFromMappedType(source), mapper), getTemplateTypeFromMappedType(target), reportErrors); } } } return Ternary.False; } function typeRelatedToDiscriminatedType(source: Type, target: UnionType) { // 1. Generate the combinations of discriminant properties & types 'source' can satisfy. // a. If the number of combinations is above a set limit, the comparison is too complex. // 2. Filter 'target' to the subset of types whose discriminants exist in the matrix. // a. If 'target' does not satisfy all discriminants in the matrix, 'source' is not related. // 3. For each type in the filtered 'target', determine if all non-discriminant properties of // 'target' are related to a property in 'source'. // // NOTE: See ~/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithDiscriminatedUnion.ts // for examples. const sourceProperties = getPropertiesOfType(source); const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); if (!sourcePropertiesFiltered) return Ternary.False; // Though we could compute the number of combinations as we generate // the matrix, this would incur additional memory overhead due to // array allocations. To reduce this overhead, we first compute // the number of combinations to ensure we will not surpass our // fixed limit before incurring the cost of any allocations: let numCombinations = 1; for (const sourceProperty of sourcePropertiesFiltered) { numCombinations *= countTypes(getTypeOfSymbol(sourceProperty)); if (numCombinations > 25) { // We've reached the complexity limit. tracing.instant(tracing.Phase.Check, "typeRelatedToDiscriminatedType_DepthLimit", { sourceId: source.id, targetId: target.id, numCombinations }); return Ternary.False; } } // Compute the set of types for each discriminant property. const sourceDiscriminantTypes: Type[][] = new Array(sourcePropertiesFiltered.length); const excludedProperties = new Set<__String>(); for (let i = 0; i < sourcePropertiesFiltered.length; i++) { const sourceProperty = sourcePropertiesFiltered[i]; const sourcePropertyType = getTypeOfSymbol(sourceProperty); sourceDiscriminantTypes[i] = sourcePropertyType.flags & TypeFlags.Union ? (sourcePropertyType as UnionType).types : [sourcePropertyType]; excludedProperties.add(sourceProperty.escapedName); } // Match each combination of the cartesian product of discriminant properties to one or more // constituents of 'target'. If any combination does not have a match then 'source' is not relatable. const discriminantCombinations = cartesianProduct(sourceDiscriminantTypes); const matchingTypes: Type[] = []; for (const combination of discriminantCombinations) { let hasMatch = false; outer: for (const type of target.types) { for (let i = 0; i < sourcePropertiesFiltered.length; i++) { const sourceProperty = sourcePropertiesFiltered[i]; const targetProperty = getPropertyOfType(type, sourceProperty.escapedName); if (!targetProperty) continue outer; if (sourceProperty === targetProperty) continue; // We compare the source property to the target in the context of a single discriminant type. const related = propertyRelatedTo(source, target, sourceProperty, targetProperty, _ => combination[i], /*reportErrors*/ false, IntersectionState.None, /*skipOptional*/ strictNullChecks || relation === comparableRelation); // If the target property could not be found, or if the properties were not related, // then this constituent is not a match. if (!related) { continue outer; } } pushIfUnique(matchingTypes, type, equateValues); hasMatch = true; } if (!hasMatch) { // We failed to match any type for this combination. return Ternary.False; } } // Compare the remaining non-discriminant properties of each match. let result = Ternary.True; for (const type of matchingTypes) { result &= propertiesRelatedTo(source, type, /*reportErrors*/ false, excludedProperties, IntersectionState.None); if (result) { result &= signaturesRelatedTo(source, type, SignatureKind.Call, /*reportStructuralErrors*/ false); if (result) { result &= signaturesRelatedTo(source, type, SignatureKind.Construct, /*reportStructuralErrors*/ false); if (result) { result &= indexTypesRelatedTo(source, type, IndexKind.String, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false, IntersectionState.None); // 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. if (result && !(isTupleType(source) && isTupleType(type))) { result &= indexTypesRelatedTo(source, type, IndexKind.Number, /*sourceIsPrimitive*/ false, /*reportStructuralErrors*/ false, IntersectionState.None); } } } } if (!result) { return result; } } return result; } function excludeProperties(properties: Symbol[], excludedProperties: Set<__String> | undefined) { if (!excludedProperties || properties.length === 0) return properties; let result: Symbol[] | undefined; for (let i = 0; i < properties.length; i++) { if (!excludedProperties.has(properties[i].escapedName)) { if (result) { result.push(properties[i]); } } else if (!result) { result = properties.slice(0, i); } } return result || properties; } function isPropertySymbolTypeRelated(sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary { const targetIsOptional = strictNullChecks && !!(getCheckFlags(targetProp) & CheckFlags.Partial); const source = getTypeOfSourceProperty(sourceProp); if (getCheckFlags(targetProp) & CheckFlags.DeferredType && !getSymbolLinks(targetProp).type) { // Rather than resolving (and normalizing) the type, relate constituent-by-constituent without performing normalization or seconadary passes const links = getSymbolLinks(targetProp); Debug.assertIsDefined(links.deferralParent); Debug.assertIsDefined(links.deferralConstituents); const unionParent = !!(links.deferralParent.flags & TypeFlags.Union); let result = unionParent ? Ternary.False : Ternary.True; const targetTypes = links.deferralConstituents; for (const targetType of targetTypes) { const related = isRelatedTo(source, targetType, /*reportErrors*/ false, /*headMessage*/ undefined, unionParent ? 0 : IntersectionState.Target); if (!unionParent) { if (!related) { // Can't assign to a target individually - have to fallback to assigning to the _whole_ intersection (which forces normalization) return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors); } result &= related; } else { if (related) { return related; } } } if (unionParent && !result && targetIsOptional) { result = isRelatedTo(source, undefinedType); } if (unionParent && !result && reportErrors) { // The easiest way to get the right errors here is to un-defer (which may be costly) // If it turns out this is too costly too often, we can replicate the error handling logic within // typeRelatedToSomeType without the discriminatable type branch (as that requires a manifest union // type on which to hand discriminable properties, which we are expressly trying to avoid here) return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors); } return result; } else { return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors, /*headMessage*/ undefined, intersectionState); } } function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean, intersectionState: IntersectionState, skipOptional: boolean): Ternary { const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp); const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp); if (sourcePropFlags & ModifierFlags.Private || targetPropFlags & ModifierFlags.Private) { if (sourceProp.valueDeclaration !== targetProp.valueDeclaration) { if (reportErrors) { if (sourcePropFlags & ModifierFlags.Private && targetPropFlags & ModifierFlags.Private) { reportError(Diagnostics.Types_have_separate_declarations_of_a_private_property_0, symbolToString(targetProp)); } else { reportError(Diagnostics.Property_0_is_private_in_type_1_but_not_in_type_2, symbolToString(targetProp), typeToString(sourcePropFlags & ModifierFlags.Private ? source : target), typeToString(sourcePropFlags & ModifierFlags.Private ? target : source)); } } return Ternary.False; } } else if (targetPropFlags & ModifierFlags.Protected) { if (!isValidOverrideOf(sourceProp, targetProp)) { if (reportErrors) { reportError(Diagnostics.Property_0_is_protected_but_type_1_is_not_a_class_derived_from_2, symbolToString(targetProp), typeToString(getDeclaringClass(sourceProp) || source), typeToString(getDeclaringClass(targetProp) || target)); } return Ternary.False; } } else if (sourcePropFlags & ModifierFlags.Protected) { if (reportErrors) { reportError(Diagnostics.Property_0_is_protected_in_type_1_but_public_in_type_2, symbolToString(targetProp), typeToString(source), typeToString(target)); } return Ternary.False; } // If the target comes from a partial union prop, allow `undefined` in the target type const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors, intersectionState); if (!related) { if (reportErrors) { reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp)); } return Ternary.False; } // When checking for comparability, be more lenient with optional properties. if (!skipOptional && sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) { // TypeScript 1.0 spec (April 2014): 3.8.3 // S is a subtype of a type T, and T is a supertype of S if ... // S' and T are object types and, for each member M in T.. // M is a property and S' contains a property N where // if M is a required property, N is also a required property // (M - property in T) // (N - property in S) if (reportErrors) { reportError(Diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2, symbolToString(targetProp), typeToString(source), typeToString(target)); } return Ternary.False; } return related; } function reportUnmatchedProperty(source: Type, target: Type, unmatchedProperty: Symbol, requireOptionalProperties: boolean) { let shouldSkipElaboration = false; // give specific error in case where private names have the same description if (unmatchedProperty.valueDeclaration && isNamedDeclaration(unmatchedProperty.valueDeclaration) && isPrivateIdentifier(unmatchedProperty.valueDeclaration.name) && source.symbol && source.symbol.flags & SymbolFlags.Class) { const privateIdentifierDescription = unmatchedProperty.valueDeclaration.name.escapedText; const symbolTableKey = getSymbolNameForPrivateIdentifier(source.symbol, privateIdentifierDescription); if (symbolTableKey && getPropertyOfType(source, symbolTableKey)) { const sourceName = factory.getDeclarationName(source.symbol.valueDeclaration); const targetName = factory.getDeclarationName(target.symbol.valueDeclaration); reportError( Diagnostics.Property_0_in_type_1_refers_to_a_different_member_that_cannot_be_accessed_from_within_type_2, diagnosticName(privateIdentifierDescription), diagnosticName(sourceName.escapedText === "" ? anon : sourceName), diagnosticName(targetName.escapedText === "" ? anon : targetName)); return; } } const props = arrayFrom(getUnmatchedProperties(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false)); if (!headMessage || (headMessage.code !== Diagnostics.Class_0_incorrectly_implements_interface_1.code && headMessage.code !== Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass.code)) { shouldSkipElaboration = true; // Retain top-level error for interface implementing issues, otherwise omit it } if (props.length === 1) { const propName = symbolToString(unmatchedProperty); reportError(Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2, propName, ...getTypeNamesForErrorDisplay(source, target)); if (length(unmatchedProperty.declarations)) { associateRelatedInfo(createDiagnosticForNode(unmatchedProperty.declarations[0], Diagnostics._0_is_declared_here, propName)); } if (shouldSkipElaboration && errorInfo) { overrideNextErrorInfo++; } } else if (tryElaborateArrayLikeErrors(source, target, /*reportErrors*/ false)) { if (props.length > 5) { // arbitrary cutoff for too-long list form reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2_and_3_more, typeToString(source), typeToString(target), map(props.slice(0, 4), p => symbolToString(p)).join(", "), props.length - 4); } else { reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2, typeToString(source), typeToString(target), map(props, p => symbolToString(p)).join(", ")); } if (shouldSkipElaboration && errorInfo) { overrideNextErrorInfo++; } } // No array like or unmatched property error - just issue top level error (errorInfo = undefined) } function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: Set<__String> | undefined, intersectionState: IntersectionState): Ternary { if (relation === identityRelation) { return propertiesIdenticalTo(source, target, excludedProperties); } let result = Ternary.True; if (isTupleType(target)) { if (isArrayType(source) || isTupleType(source)) { if (!target.target.readonly && (isReadonlyArrayType(source) || isTupleType(source) && source.target.readonly)) { return Ternary.False; } const sourceArity = getTypeReferenceArity(source); const targetArity = getTypeReferenceArity(target); const sourceRestFlag = isTupleType(source) ? source.target.combinedFlags & ElementFlags.Rest : ElementFlags.Rest; const targetRestFlag = target.target.combinedFlags & ElementFlags.Rest; const sourceMinLength = isTupleType(source) ? source.target.minLength : 0; const targetMinLength = target.target.minLength; if (!sourceRestFlag && sourceArity < targetMinLength) { if (reportErrors) { reportError(Diagnostics.Source_has_0_element_s_but_target_requires_1, sourceArity, targetMinLength); } return Ternary.False; } if (!targetRestFlag && targetArity < sourceMinLength) { if (reportErrors) { reportError(Diagnostics.Source_has_0_element_s_but_target_allows_only_1, sourceMinLength, targetArity); } return Ternary.False; } if (!targetRestFlag && sourceRestFlag) { if (reportErrors) { if (sourceMinLength < targetMinLength) { reportError(Diagnostics.Target_requires_0_element_s_but_source_may_have_fewer, targetMinLength); } else { reportError(Diagnostics.Target_allows_only_0_element_s_but_source_may_have_more, targetArity); } } return Ternary.False; } const maxArity = Math.max(sourceArity, targetArity); for (let i = 0; i < maxArity; i++) { const targetFlags = i < targetArity ? target.target.elementFlags[i] : targetRestFlag; const sourceFlags = isTupleType(source) && i < sourceArity ? source.target.elementFlags[i] : sourceRestFlag; let canExcludeDiscriminants = !!excludedProperties; if (sourceFlags && targetFlags) { if (targetFlags & ElementFlags.Variadic && !(sourceFlags & ElementFlags.Variadic) || (sourceFlags & ElementFlags.Variadic && !(targetFlags & ElementFlags.Variable))) { if (reportErrors) { reportError(Diagnostics.Element_at_index_0_is_variadic_in_one_type_but_not_in_the_other, i); } return Ternary.False; } if (targetFlags & ElementFlags.Required) { if (!(sourceFlags & ElementFlags.Required)) { if (reportErrors) { reportError(Diagnostics.Property_0_is_optional_in_type_1_but_required_in_type_2, i, typeToString(source), typeToString(target)); } return Ternary.False; } } // We can only exclude discriminant properties if we have not yet encountered a variable-length element. if (canExcludeDiscriminants) { if (sourceFlags & ElementFlags.Variable || targetFlags & ElementFlags.Variable) { canExcludeDiscriminants = false; } if (canExcludeDiscriminants && excludedProperties?.has(("" + i) as __String)) { continue; } } const sourceType = getTypeArguments(source)[Math.min(i, sourceArity - 1)]; const targetType = getTypeArguments(target)[Math.min(i, targetArity - 1)]; const targetCheckType = sourceFlags & ElementFlags.Variadic && targetFlags & ElementFlags.Rest ? createArrayType(targetType) : targetType; const related = isRelatedTo(sourceType, targetCheckType, reportErrors, /*headMessage*/ undefined, intersectionState); if (!related) { if (reportErrors) { reportIncompatibleError(Diagnostics.Types_of_property_0_are_incompatible, i); } return Ternary.False; } result &= related; } } return result; } if (target.target.combinedFlags & ElementFlags.Variable) { return Ternary.False; } } const requireOptionalProperties = (relation === subtypeRelation || relation === strictSubtypeRelation) && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source) && !isTupleType(source); const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false); if (unmatchedProperty) { if (reportErrors) { reportUnmatchedProperty(source, target, unmatchedProperty, requireOptionalProperties); } return Ternary.False; } if (isObjectLiteralType(target)) { for (const sourceProp of excludeProperties(getPropertiesOfType(source), excludedProperties)) { if (!getPropertyOfObjectType(target, sourceProp.escapedName)) { const sourceType = getTypeOfSymbol(sourceProp); if (!(sourceType === undefinedType || sourceType === undefinedWideningType || sourceType === optionalType)) { if (reportErrors) { reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(sourceProp), typeToString(target)); } return Ternary.False; } } } } // We only call this for union target types when we're attempting to do excess property checking - in those cases, we want to get _all possible props_ // from the target union, across all members const properties = getPropertiesOfType(target); const numericNamesOnly = isTupleType(source) && isTupleType(target); for (const targetProp of excludeProperties(properties, excludedProperties)) { const name = targetProp.escapedName; if (!(targetProp.flags & SymbolFlags.Prototype) && (!numericNamesOnly || isNumericLiteralName(name) || name === "length")) { const sourceProp = getPropertyOfType(source, name); if (sourceProp && sourceProp !== targetProp) { const related = propertyRelatedTo(source, target, sourceProp, targetProp, getTypeOfSymbol, reportErrors, intersectionState, relation === comparableRelation); if (!related) { return Ternary.False; } result &= related; } } } return result; } function propertiesIdenticalTo(source: Type, target: Type, excludedProperties: Set<__String> | undefined): Ternary { if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) { return Ternary.False; } const sourceProperties = excludeProperties(getPropertiesOfObjectType(source), excludedProperties); const targetProperties = excludeProperties(getPropertiesOfObjectType(target), excludedProperties); if (sourceProperties.length !== targetProperties.length) { return Ternary.False; } let result = Ternary.True; for (const sourceProp of sourceProperties) { const targetProp = getPropertyOfObjectType(target, sourceProp.escapedName); if (!targetProp) { return Ternary.False; } const related = compareProperties(sourceProp, targetProp, isRelatedTo); if (!related) { return Ternary.False; } result &= related; } return result; } function signaturesRelatedTo(source: Type, target: Type, kind: SignatureKind, reportErrors: boolean): Ternary { if (relation === identityRelation) { return signaturesIdenticalTo(source, target, kind); } if (target === anyFunctionType || source === anyFunctionType) { return Ternary.True; } const sourceIsJSConstructor = source.symbol && isJSConstructor(source.symbol.valueDeclaration); const targetIsJSConstructor = target.symbol && isJSConstructor(target.symbol.valueDeclaration); const sourceSignatures = getSignaturesOfType(source, (sourceIsJSConstructor && kind === SignatureKind.Construct) ? SignatureKind.Call : kind); const targetSignatures = getSignaturesOfType(target, (targetIsJSConstructor && kind === SignatureKind.Construct) ? SignatureKind.Call : kind); if (kind === SignatureKind.Construct && sourceSignatures.length && targetSignatures.length) { if (isAbstractConstructorType(source) && !isAbstractConstructorType(target)) { // An abstract constructor type is not assignable to a non-abstract constructor type // as it would otherwise be possible to new an abstract class. Note that the assignability // check we perform for an extends clause excludes construct signatures from the target, // so this check never proceeds. if (reportErrors) { reportError(Diagnostics.Cannot_assign_an_abstract_constructor_type_to_a_non_abstract_constructor_type); } return Ternary.False; } if (!constructorVisibilitiesAreCompatible(sourceSignatures[0], targetSignatures[0], reportErrors)) { return Ternary.False; } } let result = Ternary.True; const saveErrorInfo = captureErrorCalculationState(); const incompatibleReporter = kind === SignatureKind.Construct ? reportIncompatibleConstructSignatureReturn : reportIncompatibleCallSignatureReturn; const sourceObjectFlags = getObjectFlags(source); const targetObjectFlags = getObjectFlags(target); if (sourceObjectFlags & ObjectFlags.Instantiated && targetObjectFlags & ObjectFlags.Instantiated && source.symbol === target.symbol) { // We have instantiations of the same anonymous type (which typically will be the type of a // method). Simply do a pairwise comparison of the signatures in the two signature lists instead // of the much more expensive N * M comparison matrix we explore below. We erase type parameters // as they are known to always be the same. for (let i = 0; i < targetSignatures.length; i++) { const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, incompatibleReporter(sourceSignatures[i], targetSignatures[i])); if (!related) { return Ternary.False; } result &= related; } } else if (sourceSignatures.length === 1 && targetSignatures.length === 1) { // For simple functions (functions with a single signature) we only erase type parameters for // the comparable relation. Otherwise, if the source signature is generic, we instantiate it // in the context of the target signature before checking the relationship. Ideally we'd do // this regardless of the number of signatures, but the potential costs are prohibitive due // to the quadratic nature of the logic below. const eraseGenerics = relation === comparableRelation || !!compilerOptions.noStrictGenericChecks; const sourceSignature = first(sourceSignatures); const targetSignature = first(targetSignatures); result = signatureRelatedTo(sourceSignature, targetSignature, eraseGenerics, reportErrors, incompatibleReporter(sourceSignature, targetSignature)); if (!result && reportErrors && kind === SignatureKind.Construct && (sourceObjectFlags & targetObjectFlags) && (targetSignature.declaration?.kind === SyntaxKind.Constructor || sourceSignature.declaration?.kind === SyntaxKind.Constructor)) { const constructSignatureToString = (signature: Signature) => signatureToString(signature, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrowStyleSignature, kind); reportError(Diagnostics.Type_0_is_not_assignable_to_type_1, constructSignatureToString(sourceSignature), constructSignatureToString(targetSignature)); reportError(Diagnostics.Types_of_construct_signatures_are_incompatible); return result; } } else { outer: for (const t of targetSignatures) { // Only elaborate errors from the first failure let shouldElaborateErrors = reportErrors; for (const s of sourceSignatures) { const related = signatureRelatedTo(s, t, /*erase*/ true, shouldElaborateErrors, incompatibleReporter(s, t)); if (related) { result &= related; resetErrorInfo(saveErrorInfo); continue outer; } shouldElaborateErrors = false; } if (shouldElaborateErrors) { reportError(Diagnostics.Type_0_provides_no_match_for_the_signature_1, typeToString(source), signatureToString(t, /*enclosingDeclaration*/ undefined, /*flags*/ undefined, kind)); } return Ternary.False; } } return result; } function reportIncompatibleCallSignatureReturn(siga: Signature, sigb: Signature) { if (siga.parameters.length === 0 && sigb.parameters.length === 0) { return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target)); } return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Call_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target)); } function reportIncompatibleConstructSignatureReturn(siga: Signature, sigb: Signature) { if (siga.parameters.length === 0 && sigb.parameters.length === 0) { return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signatures_with_no_arguments_have_incompatible_return_types_0_and_1, typeToString(source), typeToString(target)); } return (source: Type, target: Type) => reportIncompatibleError(Diagnostics.Construct_signature_return_types_0_and_1_are_incompatible, typeToString(source), typeToString(target)); } /** * See signatureAssignableTo, compareSignaturesIdentical */ function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary { return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedTo, makeFunctionTypeMapper(reportUnreliableMarkers)); } function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary { const sourceSignatures = getSignaturesOfType(source, kind); const targetSignatures = getSignaturesOfType(target, kind); if (sourceSignatures.length !== targetSignatures.length) { return Ternary.False; } let result = Ternary.True; for (let i = 0; i < sourceSignatures.length; i++) { const related = compareSignaturesIdentical(sourceSignatures[i], targetSignatures[i], /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, isRelatedTo); if (!related) { return Ternary.False; } result &= related; } return result; } function eachPropertyRelatedTo(source: Type, target: Type, kind: IndexKind, reportErrors: boolean): Ternary { let result = Ternary.True; const props = source.flags & TypeFlags.Intersection ? getPropertiesOfUnionOrIntersectionType(source) : getPropertiesOfObjectType(source); for (const prop of props) { // Skip over ignored JSX and symbol-named members if (isIgnoredJsxProperty(source, prop)) { continue; } const nameType = getSymbolLinks(prop).nameType; if (nameType && nameType.flags & TypeFlags.UniqueESSymbol) { continue; } if (kind === IndexKind.String || isNumericLiteralName(prop.escapedName)) { const related = isRelatedTo(getTypeOfSymbol(prop), target, reportErrors); if (!related) { if (reportErrors) { reportError(Diagnostics.Property_0_is_incompatible_with_index_signature, symbolToString(prop)); } return Ternary.False; } result &= related; } } return result; } function indexTypeRelatedTo(sourceType: Type, targetType: Type, reportErrors: boolean) { const related = isRelatedTo(sourceType, targetType, reportErrors); if (!related && reportErrors) { reportError(Diagnostics.Index_signatures_are_incompatible); } return related; } function indexTypesRelatedTo(source: Type, target: Type, kind: IndexKind, sourceIsPrimitive: boolean, reportErrors: boolean, intersectionState: IntersectionState): Ternary { if (relation === identityRelation) { return indexTypesIdenticalTo(source, target, kind); } const targetType = getIndexTypeOfType(target, kind); if (!targetType || targetType.flags & TypeFlags.Any && !sourceIsPrimitive) { // Index signature of type any permits assignment from everything but primitives return Ternary.True; } if (isGenericMappedType(source)) { // A generic mapped type { [P in K]: T } is related to a type with an index signature // { [x: string]: U }, and optionally with an index signature { [x: number]: V }, // if T is related to U and V. return getIndexTypeOfType(target, IndexKind.String) ? isRelatedTo(getTemplateTypeFromMappedType(source), targetType, reportErrors) : Ternary.False; } const indexType = getIndexTypeOfType(source, kind) || kind === IndexKind.Number && getIndexTypeOfType(source, IndexKind.String); if (indexType) { return indexTypeRelatedTo(indexType, targetType, reportErrors); } if (!(intersectionState & IntersectionState.Source) && isObjectTypeWithInferableIndex(source)) { // Intersection constituents are never considered to have an inferred index signature let related = eachPropertyRelatedTo(source, targetType, kind, reportErrors); if (related && kind === IndexKind.String) { const numberIndexType = getIndexTypeOfType(source, IndexKind.Number); if (numberIndexType) { related &= indexTypeRelatedTo(numberIndexType, targetType, reportErrors); } } return related; } if (reportErrors) { reportError(Diagnostics.Index_signature_is_missing_in_type_0, typeToString(source)); } return Ternary.False; } function indexTypesIdenticalTo(source: Type, target: Type, indexKind: IndexKind): Ternary { const targetInfo = getIndexInfoOfType(target, indexKind); const sourceInfo = getIndexInfoOfType(source, indexKind); if (!sourceInfo && !targetInfo) { return Ternary.True; } if (sourceInfo && targetInfo && sourceInfo.isReadonly === targetInfo.isReadonly) { return isRelatedTo(sourceInfo.type, targetInfo.type); } return Ternary.False; } function constructorVisibilitiesAreCompatible(sourceSignature: Signature, targetSignature: Signature, reportErrors: boolean) { if (!sourceSignature.declaration || !targetSignature.declaration) { return true; } const sourceAccessibility = getSelectedEffectiveModifierFlags(sourceSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier); const targetAccessibility = getSelectedEffectiveModifierFlags(targetSignature.declaration, ModifierFlags.NonPublicAccessibilityModifier); // A public, protected and private signature is assignable to a private signature. if (targetAccessibility === ModifierFlags.Private) { return true; } // A public and protected signature is assignable to a protected signature. if (targetAccessibility === ModifierFlags.Protected && sourceAccessibility !== ModifierFlags.Private) { return true; } // Only a public signature is assignable to public signature. if (targetAccessibility !== ModifierFlags.Protected && !sourceAccessibility) { return true; } if (reportErrors) { reportError(Diagnostics.Cannot_assign_a_0_constructor_type_to_a_1_constructor_type, visibilityToString(sourceAccessibility), visibilityToString(targetAccessibility)); } return false; } } function typeCouldHaveTopLevelSingletonTypes(type: Type): boolean { // Okay, yes, 'boolean' is a union of 'true | false', but that's not useful // in error reporting scenarios. If you need to use this function but that detail matters, // feel free to add a flag. if (type.flags & TypeFlags.Boolean) { return false; } if (type.flags & TypeFlags.UnionOrIntersection) { return !!forEach((type as IntersectionType).types, typeCouldHaveTopLevelSingletonTypes); } if (type.flags & TypeFlags.Instantiable) { const constraint = getConstraintOfType(type); if (constraint && constraint !== type) { return typeCouldHaveTopLevelSingletonTypes(constraint); } } return isUnitType(type) || !!(type.flags & TypeFlags.TemplateLiteral); } function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) { return findMatchingDiscriminantType(source, target, isRelatedTo, /*skipPartial*/ true) || findMatchingTypeReferenceOrTypeAliasReference(source, target) || findBestTypeForObjectLiteral(source, target) || findBestTypeForInvokable(source, target) || findMostOverlappyType(source, target); } function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: undefined, skipPartial?: boolean): Type | undefined; function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type, skipPartial?: boolean): Type; function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type, skipPartial?: boolean) { // undefined=unknown, true=discriminated, false=not discriminated // The state of each type progresses from left to right. Discriminated types stop at 'true'. const discriminable = target.types.map(_ => undefined) as (boolean | undefined)[]; for (const [getDiscriminatingType, propertyName] of discriminators) { const targetProp = getUnionOrIntersectionProperty(target, propertyName); if (skipPartial && targetProp && getCheckFlags(targetProp) & CheckFlags.ReadPartial) { continue; } let i = 0; for (const type of target.types) { const targetType = getTypeOfPropertyOfType(type, propertyName); if (targetType && related(getDiscriminatingType(), targetType)) { discriminable[i] = discriminable[i] === undefined ? true : discriminable[i]; } else { discriminable[i] = false; } i++; } } const match = discriminable.indexOf(/*searchElement*/ true); if (match === -1) { return defaultValue; } // make sure exactly 1 matches before returning it let nextMatch = discriminable.indexOf(/*searchElement*/ true, match + 1); while (nextMatch !== -1) { if (!isTypeIdenticalTo(target.types[match], target.types[nextMatch])) { return defaultValue; } nextMatch = discriminable.indexOf(/*searchElement*/ true, nextMatch + 1); } return target.types[match]; } /** * A type is 'weak' if it is an object type with at least one optional property * and no required properties, call/construct signatures or index signatures */ function isWeakType(type: Type): boolean { if (type.flags & TypeFlags.Object) { const resolved = resolveStructuredTypeMembers(type); return resolved.callSignatures.length === 0 && resolved.constructSignatures.length === 0 && !resolved.stringIndexInfo && !resolved.numberIndexInfo && resolved.properties.length > 0 && every(resolved.properties, p => !!(p.flags & SymbolFlags.Optional)); } if (type.flags & TypeFlags.Intersection) { return every((type).types, isWeakType); } return false; } function hasCommonProperties(source: Type, target: Type, isComparingJsxAttributes: boolean) { for (const prop of getPropertiesOfType(source)) { if (isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) { return true; } } return false; } // Return a type reference where the source type parameter is replaced with the target marker // type, and flag the result as a marker type reference. function getMarkerTypeReference(type: GenericType, source: TypeParameter, target: Type) { const result = createTypeReference(type, map(type.typeParameters, t => t === source ? target : t)); result.objectFlags |= ObjectFlags.MarkerType; return result; } function getAliasVariances(symbol: Symbol) { const links = getSymbolLinks(symbol); return getVariancesWorker(links.typeParameters, links, (_links, param, marker) => { const type = getTypeAliasInstantiation(symbol, instantiateTypes(links.typeParameters!, makeUnaryTypeMapper(param, marker))); type.aliasTypeArgumentsContainsMarker = true; return type; }); } // Return an array containing the variance of each type parameter. The variance is effectively // a digest of the type comparisons that occur for each type argument when instantiations of the // generic type are structurally compared. We infer the variance information by comparing // instantiations of the generic type for type arguments with known relations. The function // returns the emptyArray singleton when invoked recursively for the given generic type. function getVariancesWorker(typeParameters: readonly TypeParameter[] = emptyArray, cache: TCache, createMarkerType: (input: TCache, param: TypeParameter, marker: Type) => Type): VarianceFlags[] { let variances = cache.variances; if (!variances) { tracing.push(tracing.Phase.Check, "getVariancesWorker", { arity: typeParameters.length, id: (cache as any).id ?? (cache as any).declaredType?.id ?? -1 }); // The emptyArray singleton is used to signal a recursive invocation. cache.variances = emptyArray; variances = []; for (const tp of typeParameters) { let unmeasurable = false; let unreliable = false; const oldHandler = outofbandVarianceMarkerHandler; outofbandVarianceMarkerHandler = (onlyUnreliable) => onlyUnreliable ? unreliable = true : unmeasurable = true; // We first compare instantiations where the type parameter is replaced with // marker types that have a known subtype relationship. From this we can infer // invariance, covariance, contravariance or bivariance. const typeWithSuper = createMarkerType(cache, tp, markerSuperType); const typeWithSub = createMarkerType(cache, tp, markerSubType); let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? VarianceFlags.Covariant : 0) | (isTypeAssignableTo(typeWithSuper, typeWithSub) ? VarianceFlags.Contravariant : 0); // If the instantiations appear to be related bivariantly it may be because the // type parameter is independent (i.e. it isn't witnessed anywhere in the generic // type). To determine this we compare instantiations where the type parameter is // replaced with marker types that are known to be unrelated. if (variance === VarianceFlags.Bivariant && isTypeAssignableTo(createMarkerType(cache, tp, markerOtherType), typeWithSuper)) { variance = VarianceFlags.Independent; } outofbandVarianceMarkerHandler = oldHandler; if (unmeasurable || unreliable) { if (unmeasurable) { variance |= VarianceFlags.Unmeasurable; } if (unreliable) { variance |= VarianceFlags.Unreliable; } } variances.push(variance); } cache.variances = variances; tracing.pop(); } return variances; } function getVariances(type: GenericType): VarianceFlags[] { // Arrays and tuples are known to be covariant, no need to spend time computing this. if (type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple) { return arrayVariances; } return getVariancesWorker(type.typeParameters, type, getMarkerTypeReference); } // Return true if the given type reference has a 'void' type argument for a covariant type parameter. // See comment at call in recursiveTypeRelatedTo for when this case matters. function hasCovariantVoidArgument(typeArguments: readonly Type[], variances: VarianceFlags[]): boolean { for (let i = 0; i < variances.length; i++) { if ((variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Covariant && typeArguments[i].flags & TypeFlags.Void) { return true; } } return false; } function isUnconstrainedTypeParameter(type: Type) { return type.flags & TypeFlags.TypeParameter && !getConstraintOfTypeParameter(type); } function isNonDeferredTypeReference(type: Type): type is TypeReference { return !!(getObjectFlags(type) & ObjectFlags.Reference) && !(type).node; } function isTypeReferenceWithGenericArguments(type: Type): boolean { return isNonDeferredTypeReference(type) && some(getTypeArguments(type), t => isUnconstrainedTypeParameter(t) || isTypeReferenceWithGenericArguments(t)); } /** * getTypeReferenceId(A) returns "111=0-12=1" * where A.id=111 and number.id=12 */ function getTypeReferenceId(type: TypeReference, typeParameters: Type[], depth = 0) { let result = "" + type.target.id; for (const t of getTypeArguments(type)) { if (isUnconstrainedTypeParameter(t)) { let index = typeParameters.indexOf(t); if (index < 0) { index = typeParameters.length; typeParameters.push(t); } result += "=" + index; } else if (depth < 4 && isTypeReferenceWithGenericArguments(t)) { result += "<" + getTypeReferenceId(t as TypeReference, typeParameters, depth + 1) + ">"; } else { result += "-" + t.id; } } return result; } /** * To improve caching, the relation key for two generic types uses the target's id plus ids of the type parameters. * For other cases, the types ids are used. */ function getRelationKey(source: Type, target: Type, intersectionState: IntersectionState, relation: ESMap) { if (relation === identityRelation && source.id > target.id) { const temp = source; source = target; target = temp; } const postFix = intersectionState ? ":" + intersectionState : ""; if (isTypeReferenceWithGenericArguments(source) && isTypeReferenceWithGenericArguments(target)) { const typeParameters: Type[] = []; return getTypeReferenceId(source, typeParameters) + "," + getTypeReferenceId(target, typeParameters) + postFix; } return source.id + "," + target.id + postFix; } // Invoke the callback for each underlying property symbol of the given symbol and return the first // value that isn't undefined. function forEachProperty(prop: Symbol, callback: (p: Symbol) => T): T | undefined { if (getCheckFlags(prop) & CheckFlags.Synthetic) { for (const t of (prop).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)!) : undefined; } // Return the inherited type of the given property or undefined if property doesn't exist in a base class. function getTypeOfPropertyInBaseClass(property: Symbol) { const classType = getDeclaringClass(property); const baseClassType = classType && getBaseTypes(classType)[0]; return baseClassType && getTypeOfPropertyOfType(baseClassType, property.escapedName); } // Return true if some underlying source property is declared in a class that derives // from the given base class. function isPropertyInClassDerivedFrom(prop: Symbol, baseClass: Type | undefined) { return forEachProperty(prop, sp => { const sourceClass = getDeclaringClass(sp); return sourceClass ? hasBaseType(sourceClass, baseClass) : false; }); } // Return true if source property is a valid override of protected parts of target property. function isValidOverrideOf(sourceProp: Symbol, targetProp: Symbol) { return !forEachProperty(targetProp, tp => getDeclarationModifierFlagsFromSymbol(tp) & ModifierFlags.Protected ? !isPropertyInClassDerivedFrom(sourceProp, getDeclaringClass(tp)) : false); } // Return true if the given class derives from each of the declaring classes of the protected // constituents of the given property. function isClassDerivedFromDeclaringClasses(checkClass: Type, prop: Symbol) { return forEachProperty(prop, p => getDeclarationModifierFlagsFromSymbol(p) & ModifierFlags.Protected ? !hasBaseType(checkClass, getDeclaringClass(p)) : false) ? undefined : checkClass; } // Return true if the given type is deeply nested. We consider this to be the case when structural type comparisons // for 5 or more occurrences or instantiations of the type have been recorded on the given stack. It is possible, // though highly unlikely, for this test to be true in a situation where a chain of instantiations is not infinitely // expanding. Effectively, we will generate a false positive when two types are structurally equal to at least 5 // levels, but unequal at some level beyond that. // In addition, this will also detect when an indexed access has been chained off of 5 or more times (which is essentially // the dual of the structural comparison), and likewise mark the type as deeply nested, potentially adding false positives // for finite but deeply expanding indexed accesses (eg, for `Q[P1][P2][P3][P4][P5]`). // It also detects when a recursive type reference has expanded 5 or more times, eg, if the true branch of // `type A = null extends T ? [A>] : [T]` // has expanded into `[A>>>>>]` // in such cases we need to terminate the expansion, and we do so here. function isDeeplyNestedType(type: Type, stack: Type[], depth: number): boolean { if (depth >= 5) { const identity = getRecursionIdentity(type); if (identity) { let count = 0; for (let i = 0; i < depth; i++) { if (getRecursionIdentity(stack[i]) === identity) { count++; if (count >= 5) { return true; } } } } } return false; } // Types with constituents that could circularly reference the type have a recursion identity. The recursion // identity is some object that is common to instantiations of the type with the same origin. function getRecursionIdentity(type: Type): object | undefined { if (type.flags & TypeFlags.Object && !isObjectOrArrayLiteralType(type)) { if (getObjectFlags(type) && ObjectFlags.Reference && (type as TypeReference).node) { // Deferred type references are tracked through their associated AST node. This gives us finer // granularity than using their associated target because each manifest type reference has a // unique AST node. return (type as TypeReference).node; } if (type.symbol && !(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) { // We track all object types that have an associated symbol (representing the origin of the type), but // exclude the static side of classes from this check since it shares its symbol with the instance side. return type.symbol; } if (isTupleType(type)) { // Tuple types are tracked through their target type return type.target; } } if (type.flags & TypeFlags.IndexedAccess) { // Identity is the leftmost object type in a chain of indexed accesses, eg, in A[P][Q] it is A do { type = (type as IndexedAccessType).objectType; } while (type.flags & TypeFlags.IndexedAccess); return type; } if (type.flags & TypeFlags.Conditional) { // The root object represents the origin of the conditional type return (type as ConditionalType).root; } return undefined; } function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean { return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False; } function compareProperties(sourceProp: Symbol, targetProp: Symbol, compareTypes: (source: Type, target: Type) => Ternary): Ternary { // Two members are considered identical when // - they are public properties with identical names, optionality, and types, // - they are private or protected properties originating in the same declaration and having identical types if (sourceProp === targetProp) { return Ternary.True; } const sourcePropAccessibility = getDeclarationModifierFlagsFromSymbol(sourceProp) & ModifierFlags.NonPublicAccessibilityModifier; const targetPropAccessibility = getDeclarationModifierFlagsFromSymbol(targetProp) & ModifierFlags.NonPublicAccessibilityModifier; if (sourcePropAccessibility !== targetPropAccessibility) { return Ternary.False; } if (sourcePropAccessibility) { if (getTargetSymbol(sourceProp) !== getTargetSymbol(targetProp)) { return Ternary.False; } } else { if ((sourceProp.flags & SymbolFlags.Optional) !== (targetProp.flags & SymbolFlags.Optional)) { return Ternary.False; } } if (isReadonlySymbol(sourceProp) !== isReadonlySymbol(targetProp)) { return Ternary.False; } return compareTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); } function isMatchingSignature(source: Signature, target: Signature, partialMatch: boolean) { const sourceParameterCount = getParameterCount(source); const targetParameterCount = getParameterCount(target); const sourceMinArgumentCount = getMinArgumentCount(source); const targetMinArgumentCount = getMinArgumentCount(target); const sourceHasRestParameter = hasEffectiveRestParameter(source); const targetHasRestParameter = hasEffectiveRestParameter(target); // A source signature matches a target signature if the two signatures have the same number of required, // optional, and rest parameters. if (sourceParameterCount === targetParameterCount && sourceMinArgumentCount === targetMinArgumentCount && sourceHasRestParameter === targetHasRestParameter) { return true; } // A source signature partially matches a target signature if the target signature has no fewer required // parameters if (partialMatch && sourceMinArgumentCount <= targetMinArgumentCount) { return true; } return false; } /** * See signatureRelatedTo, compareSignaturesIdentical */ function compareSignaturesIdentical(source: Signature, target: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary { // TODO (drosen): De-duplicate code between related functions. if (source === target) { return Ternary.True; } if (!(isMatchingSignature(source, target, partialMatch))) { return Ternary.False; } // Check that the two signatures have the same number of type parameters. if (length(source.typeParameters) !== length(target.typeParameters)) { return Ternary.False; } // Check that type parameter constraints and defaults match. If they do, instantiate the source // signature with the type parameters of the target signature and continue the comparison. if (target.typeParameters) { const mapper = createTypeMapper(source.typeParameters!, target.typeParameters); for (let i = 0; i < target.typeParameters.length; i++) { const s = source.typeParameters![i]; const t = target.typeParameters[i]; if (!(s === t || compareTypes(instantiateType(getConstraintFromTypeParameter(s), mapper) || unknownType, getConstraintFromTypeParameter(t) || unknownType) && compareTypes(instantiateType(getDefaultFromTypeParameter(s), mapper) || unknownType, getDefaultFromTypeParameter(t) || unknownType))) { return Ternary.False; } } source = instantiateSignature(source, mapper, /*eraseTypeParameters*/ true); } let result = Ternary.True; if (!ignoreThisTypes) { const sourceThisType = getThisTypeOfSignature(source); if (sourceThisType) { const targetThisType = getThisTypeOfSignature(target); if (targetThisType) { const related = compareTypes(sourceThisType, targetThisType); if (!related) { return Ternary.False; } result &= related; } } } const targetLen = getParameterCount(target); for (let i = 0; i < targetLen; i++) { const s = getTypeAtPosition(source, i); const t = getTypeAtPosition(target, i); const related = compareTypes(t, s); if (!related) { return Ternary.False; } result &= related; } if (!ignoreReturnTypes) { const sourceTypePredicate = getTypePredicateOfSignature(source); const targetTypePredicate = getTypePredicateOfSignature(target); result &= sourceTypePredicate || targetTypePredicate ? compareTypePredicatesIdentical(sourceTypePredicate, targetTypePredicate, compareTypes) : compareTypes(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); } return result; } function compareTypePredicatesIdentical(source: TypePredicate | undefined, target: TypePredicate | undefined, compareTypes: (s: Type, t: Type) => Ternary): Ternary { return !(source && target && typePredicateKindsMatch(source, target)) ? Ternary.False : source.type === target.type ? Ternary.True : source.type && target.type ? compareTypes(source.type, target.type) : Ternary.False; } function literalTypesWithSameBaseType(types: Type[]): boolean { let commonBaseType: Type | undefined; for (const t of types) { const baseType = getBaseTypeOfLiteralType(t); if (!commonBaseType) { commonBaseType = baseType; } if (baseType === t || baseType !== commonBaseType) { return false; } } return true; } // When the candidate types are all literal types with the same base type, return a union // of those literal types. Otherwise, return the leftmost type for which no type to the // right is a supertype. function getSupertypeOrUnion(types: Type[]): Type { return literalTypesWithSameBaseType(types) ? getUnionType(types) : reduceLeft(types, (s, t) => isTypeSubtypeOf(s, t) ? t : s)!; } function getCommonSupertype(types: Type[]): Type { if (!strictNullChecks) { return getSupertypeOrUnion(types); } const primaryTypes = filter(types, t => !(t.flags & TypeFlags.Nullable)); return primaryTypes.length ? getNullableType(getSupertypeOrUnion(primaryTypes), getFalsyFlagsOfTypes(types) & TypeFlags.Nullable) : getUnionType(types, UnionReduction.Subtype); } // Return the leftmost type for which no type to the right is a subtype. function getCommonSubtype(types: Type[]) { return reduceLeft(types, (s, t) => isTypeSubtypeOf(t, s) ? t : s)!; } function isArrayType(type: Type): type is TypeReference { return !!(getObjectFlags(type) & ObjectFlags.Reference) && ((type).target === globalArrayType || (type).target === globalReadonlyArrayType); } function isReadonlyArrayType(type: Type): boolean { return !!(getObjectFlags(type) & ObjectFlags.Reference) && (type).target === globalReadonlyArrayType; } function isMutableArrayOrTuple(type: Type): boolean { return isArrayType(type) && !isReadonlyArrayType(type) || isTupleType(type) && !type.target.readonly; } function getElementTypeOfArrayType(type: Type): Type | undefined { return isArrayType(type) ? getTypeArguments(type)[0] : undefined; } function isArrayLikeType(type: Type): boolean { // A type is array-like if it is a reference to the global Array or global ReadonlyArray type, // or if it is not the undefined or null type and if it is assignable to ReadonlyArray return isArrayType(type) || !(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType); } function isEmptyArrayLiteralType(type: Type): boolean { const elementType = isArrayType(type) ? getTypeArguments(type)[0] : undefined; return elementType === undefinedWideningType || elementType === implicitNeverType; } function isTupleLikeType(type: Type): boolean { return isTupleType(type) || !!getPropertyOfType(type, "0" as __String); } function isArrayOrTupleLikeType(type: Type): boolean { return isArrayLikeType(type) || isTupleLikeType(type); } function getTupleElementType(type: Type, index: number) { const propType = getTypeOfPropertyOfType(type, "" + index as __String); if (propType) { return propType; } if (everyType(type, isTupleType)) { return mapType(type, t => getRestTypeOfTupleType(t) || undefinedType); } return undefined; } function isNeitherUnitTypeNorNever(type: Type): boolean { return !(type.flags & (TypeFlags.Unit | TypeFlags.Never)); } function isUnitType(type: Type): boolean { return !!(type.flags & TypeFlags.Unit); } function isLiteralType(type: Type): boolean { return type.flags & TypeFlags.Boolean ? true : type.flags & TypeFlags.Union ? type.flags & TypeFlags.EnumLiteral ? true : every((type).types, isUnitType) : isUnitType(type); } function getBaseTypeOfLiteralType(type: Type): Type { return type.flags & TypeFlags.EnumLiteral ? getBaseTypeOfEnumLiteralType(type) : type.flags & TypeFlags.StringLiteral ? stringType : type.flags & TypeFlags.NumberLiteral ? numberType : type.flags & TypeFlags.BigIntLiteral ? bigintType : type.flags & TypeFlags.BooleanLiteral ? booleanType : type.flags & TypeFlags.Union ? getUnionType(sameMap((type).types, getBaseTypeOfLiteralType)) : type; } function getWidenedLiteralType(type: Type): Type { return type.flags & TypeFlags.EnumLiteral && isFreshLiteralType(type) ? getBaseTypeOfEnumLiteralType(type) : 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 ? getUnionType(sameMap((type).types, getWidenedLiteralType)) : type; } function getWidenedUniqueESSymbolType(type: Type): Type { return type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : type.flags & TypeFlags.Union ? getUnionType(sameMap((type).types, getWidenedUniqueESSymbolType)) : type; } function getWidenedLiteralLikeTypeForContextualType(type: Type, contextualType: Type | undefined) { if (!isLiteralOfContextualType(type, contextualType)) { type = getWidenedUniqueESSymbolType(getWidenedLiteralType(type)); } return type; } function getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, isAsync: boolean) { if (type && isUnitType(type)) { const contextualType = !contextualSignatureReturnType ? undefined : isAsync ? getPromisedTypeOfPromise(contextualSignatureReturnType) : contextualSignatureReturnType; type = getWidenedLiteralLikeTypeForContextualType(type, contextualType); } return type; } function getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(type: Type | undefined, contextualSignatureReturnType: Type | undefined, kind: IterationTypeKind, isAsyncGenerator: boolean) { if (type && isUnitType(type)) { const contextualType = !contextualSignatureReturnType ? undefined : getIterationTypeOfGeneratorFunctionReturnType(kind, contextualSignatureReturnType, isAsyncGenerator); type = getWidenedLiteralLikeTypeForContextualType(type, contextualType); } return type; } /** * Check if a Type was written as a tuple type literal. * Prefer using isTupleLikeType() unless the use of `elementTypes`/`getTypeArguments` is required. */ function isTupleType(type: Type): type is TupleTypeReference { return !!(getObjectFlags(type) & ObjectFlags.Reference && (type).target.objectFlags & ObjectFlags.Tuple); } function isGenericTupleType(type: Type): type is TupleTypeReference { return isTupleType(type) && !!(type.target.combinedFlags & ElementFlags.Variadic); } function isSingleElementGenericTupleType(type: Type): type is TupleTypeReference { return isGenericTupleType(type) && type.target.elementFlags.length === 1; } function getRestTypeOfTupleType(type: TupleTypeReference) { return getElementTypeOfSliceOfTupleType(type, type.target.fixedLength); } function getRestArrayTypeOfTupleType(type: TupleTypeReference) { const restType = getRestTypeOfTupleType(type); return restType && createArrayType(restType); } function getEndLengthOfType(type: Type) { return isTupleType(type) ? getTypeReferenceArity(type) - findLastIndex(type.target.elementFlags, f => !(f & (ElementFlags.Required | ElementFlags.Optional))) - 1 : 0; } function getElementTypeOfSliceOfTupleType(type: TupleTypeReference, index: number, endSkipCount = 0, writing = false) { const length = getTypeReferenceArity(type) - endSkipCount; if (index < length) { const typeArguments = getTypeArguments(type); const elementTypes: Type[] = []; for (let i = index; i < length; i++) { const t = typeArguments[i]; elementTypes.push(type.target.elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessType(t, numberType) : t); } return writing ? getIntersectionType(elementTypes) : getUnionType(elementTypes); } return undefined; } function isTupleTypeStructureMatching(t1: TupleTypeReference, t2: TupleTypeReference) { return getTypeReferenceArity(t1) === getTypeReferenceArity(t2) && every(t1.target.elementFlags, (f, i) => (f & ElementFlags.Variable) === (t2.target.elementFlags[i] & ElementFlags.Variable)); } function isZeroBigInt({value}: BigIntLiteralType) { return value.base10Value === "0"; } function getFalsyFlagsOfTypes(types: Type[]): TypeFlags { let result: TypeFlags = 0; for (const t of types) { result |= getFalsyFlags(t); } return result; } // Returns the String, Number, Boolean, StringLiteral, NumberLiteral, BooleanLiteral, Void, Undefined, or Null // flags for the string, number, boolean, "", 0, false, void, undefined, or null types respectively. Returns // no flags for all other types (including non-falsy literal types). function getFalsyFlags(type: Type): TypeFlags { return type.flags & TypeFlags.Union ? getFalsyFlagsOfTypes((type).types) : type.flags & TypeFlags.StringLiteral ? (type).value === "" ? TypeFlags.StringLiteral : 0 : type.flags & TypeFlags.NumberLiteral ? (type).value === 0 ? TypeFlags.NumberLiteral : 0 : type.flags & TypeFlags.BigIntLiteral ? isZeroBigInt(type) ? TypeFlags.BigIntLiteral : 0 : type.flags & TypeFlags.BooleanLiteral ? (type === falseType || type === regularFalseType) ? TypeFlags.BooleanLiteral : 0 : type.flags & TypeFlags.PossiblyFalsy; } function removeDefinitelyFalsyTypes(type: Type): Type { return getFalsyFlags(type) & TypeFlags.DefinitelyFalsy ? filterType(type, t => !(getFalsyFlags(t) & TypeFlags.DefinitelyFalsy)) : type; } function extractDefinitelyFalsyTypes(type: Type): Type { return mapType(type, getDefinitelyFalsyPartOfType); } function getDefinitelyFalsyPartOfType(type: Type): Type { return type.flags & TypeFlags.String ? emptyStringType : type.flags & TypeFlags.Number ? zeroType : type.flags & TypeFlags.BigInt ? zeroBigIntType : type === regularFalseType || type === falseType || type.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null | TypeFlags.AnyOrUnknown) || type.flags & TypeFlags.StringLiteral && (type).value === "" || type.flags & TypeFlags.NumberLiteral && (type).value === 0 || type.flags & TypeFlags.BigIntLiteral && isZeroBigInt(type) ? 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): Type { Debug.assert(strictNullChecks); return type.flags & TypeFlags.Undefined ? type : getUnionType([type, undefinedType]); } function getGlobalNonNullableTypeInstantiation(type: Type) { if (!deferredGlobalNonNullableTypeAlias) { deferredGlobalNonNullableTypeAlias = getGlobalSymbol("NonNullable" as __String, SymbolFlags.TypeAlias, /*diagnostic*/ undefined) || unknownSymbol; } // Use NonNullable global type alias if available to improve quick info/declaration emit if (deferredGlobalNonNullableTypeAlias !== unknownSymbol) { return getTypeAliasInstantiation(deferredGlobalNonNullableTypeAlias, [type]); } return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); // Type alias unavailable, fall back to non-higher-order behavior } function getNonNullableType(type: Type): Type { return strictNullChecks ? getGlobalNonNullableTypeInstantiation(type) : type; } function addOptionalTypeMarker(type: Type) { return strictNullChecks ? getUnionType([type, optionalType]) : type; } function isNotOptionalTypeMarker(type: Type) { return type !== optionalType; } function removeOptionalTypeMarker(type: Type): Type { return strictNullChecks ? filterType(type, isNotOptionalTypeMarker) : 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; } /** * Is source potentially coercible to target type under `==`. * Assumes that `source` is a constituent of a union, hence * the boolean literal flag on the LHS, but not on the RHS. * * This does not fully replicate the semantics of `==`. The * intention is to catch cases that are clearly not right. * * Comparing (string | number) to number should not remove the * string element. * * Comparing (string | number) to 1 will remove the string * element, though this is not sound. This is a pragmatic * choice. * * @see narrowTypeByEquality * * @param source * @param target */ function isCoercibleUnderDoubleEquals(source: Type, target: Type): boolean { return ((source.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.BooleanLiteral)) !== 0) && ((target.flags & (TypeFlags.Number | TypeFlags.String | TypeFlags.Boolean)) !== 0); } /** * Return true if type was inferred from an object literal, written as an object type literal, or is the shape of a module * with no call or construct signatures. */ function isObjectTypeWithInferableIndex(type: Type): boolean { return type.flags & TypeFlags.Intersection ? every((type).types, isObjectTypeWithInferableIndex) : !!(type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 && !typeHasCallOrConstructSignatures(type)) || !!(getObjectFlags(type) & ObjectFlags.ReverseMapped && isObjectTypeWithInferableIndex((type as ReverseMappedType).source)); } function createSymbolWithType(source: Symbol, type: Type | undefined) { const symbol = createSymbol(source.flags, source.escapedName, getCheckFlags(source) & CheckFlags.Readonly); symbol.declarations = source.declarations; symbol.parent = source.parent; symbol.type = type; symbol.target = source; if (source.valueDeclaration) { symbol.valueDeclaration = source.valueDeclaration; } const nameType = getSymbolLinks(source).nameType; if (nameType) { symbol.nameType = nameType; } return symbol; } function transformTypeOfMembers(type: Type, f: (propertyType: Type) => Type) { const members = createSymbolTable(); for (const property of getPropertiesOfObjectType(type)) { const original = getTypeOfSymbol(property); const updated = f(original); members.set(property.escapedName, updated === original ? property : createSymbolWithType(property, updated)); } return members; } /** * If the the provided object literal is subject to the excess properties check, * create a new that is exempt. Recursively mark object literal members as exempt. * Leave signatures alone since they are not subject to the check. */ function getRegularTypeOfObjectLiteral(type: Type): Type { if (!(isObjectLiteralType(type) && getObjectFlags(type) & ObjectFlags.FreshLiteral)) { return type; } const regularType = (type).regularType; if (regularType) { return regularType; } const resolved = type; const members = transformTypeOfMembers(type, getRegularTypeOfObjectLiteral); const regularNew = createAnonymousType(resolved.symbol, members, resolved.callSignatures, resolved.constructSignatures, resolved.stringIndexInfo, resolved.numberIndexInfo); regularNew.flags = resolved.flags; regularNew.objectFlags |= resolved.objectFlags & ~ObjectFlags.FreshLiteral; (type).regularType = regularNew; return regularNew; } function createWideningContext(parent: WideningContext | undefined, propertyName: __String | undefined, siblings: Type[] | undefined): WideningContext { return { parent, propertyName, siblings, resolvedProperties: undefined }; } function getSiblingsOfContext(context: WideningContext): Type[] { if (!context.siblings) { const siblings: Type[] = []; for (const type of getSiblingsOfContext(context.parent!)) { if (isObjectLiteralType(type)) { const prop = getPropertyOfObjectType(type, context.propertyName!); if (prop) { forEachType(getTypeOfSymbol(prop), t => { siblings.push(t); }); } } } context.siblings = siblings; } return context.siblings; } function getPropertiesOfContext(context: WideningContext): Symbol[] { if (!context.resolvedProperties) { const names = new Map() as UnderscoreEscapedMap; for (const t of getSiblingsOfContext(context)) { if (isObjectLiteralType(t) && !(getObjectFlags(t) & ObjectFlags.ContainsSpread)) { for (const prop of getPropertiesOfType(t)) { names.set(prop.escapedName, prop); } } } context.resolvedProperties = arrayFrom(names.values()); } return context.resolvedProperties; } function getWidenedProperty(prop: Symbol, context: WideningContext | undefined): Symbol { if (!(prop.flags & SymbolFlags.Property)) { // Since get accessors already widen their return value there is no need to // widen accessor based properties here. return prop; } const original = getTypeOfSymbol(prop); const propContext = context && createWideningContext(context, prop.escapedName, /*siblings*/ undefined); const widened = getWidenedTypeWithContext(original, propContext); return widened === original ? prop : createSymbolWithType(prop, widened); } function getUndefinedProperty(prop: Symbol) { const cached = undefinedProperties.get(prop.escapedName); if (cached) { return cached; } const result = createSymbolWithType(prop, undefinedType); 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 stringIndexInfo = getIndexInfoOfType(type, IndexKind.String); const numberIndexInfo = getIndexInfoOfType(type, IndexKind.Number); const result = createAnonymousType(type.symbol, members, emptyArray, emptyArray, stringIndexInfo && createIndexInfo(getWidenedType(stringIndexInfo.type), stringIndexInfo.isReadonly), numberIndexInfo && createIndexInfo(getWidenedType(numberIndexInfo.type), numberIndexInfo.isReadonly)); result.objectFlags |= (getObjectFlags(type) & (ObjectFlags.JSLiteral | ObjectFlags.NonInferrableType)); // Retain js literal flag through widening return result; } function getWidenedType(type: Type) { return getWidenedTypeWithContext(type, /*context*/ undefined); } function getWidenedTypeWithContext(type: Type, context: WideningContext | undefined): Type { if (getObjectFlags(type) & ObjectFlags.RequiresWidening) { if (context === undefined && type.widened) { return type.widened; } let result: Type | undefined; if (type.flags & (TypeFlags.Any | TypeFlags.Nullable)) { result = anyType; } else if (isObjectLiteralType(type)) { result = getWidenedTypeOfObjectLiteral(type, context); } else if (type.flags & TypeFlags.Union) { const unionContext = context || createWideningContext(/*parent*/ undefined, /*propertyName*/ undefined, (type).types); const widenedTypes = sameMap((type).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).types, getWidenedType)); } else if (isArrayType(type) || isTupleType(type)) { result = createTypeReference(type.target, sameMap(getTypeArguments(type), getWidenedType)); } if (result && context === undefined) { type.widened = result; } return result || type; } return type; } /** * Reports implicit any errors that occur as a result of widening 'null' and 'undefined' * to 'any'. A call to reportWideningErrorsInType is normally accompanied by a call to * getWidenedType. But in some cases getWidenedType is called without reporting errors * (type argument inference is an example). * * The return value indicates whether an error was in fact reported. The particular circumstances * are on a best effort basis. Currently, if the null or undefined that causes widening is inside * an object literal property (arbitrarily deeply), this function reports an error. If no error is * reported, reportImplicitAnyError is a suitable fallback to report a general error. */ function reportWideningErrorsInType(type: Type): boolean { let errorReported = false; if (getObjectFlags(type) & ObjectFlags.ContainsWideningType) { if (type.flags & TypeFlags.Union) { if (some((type).types, isEmptyObjectType)) { errorReported = true; } else { for (const t of (type).types) { if (reportWideningErrorsInType(t)) { errorReported = true; } } } } if (isArrayType(type) || isTupleType(type)) { for (const t of getTypeArguments(type)) { if (reportWideningErrorsInType(t)) { errorReported = true; } } } if (isObjectLiteralType(type)) { for (const p of getPropertiesOfObjectType(type)) { const t = getTypeOfSymbol(p); if (getObjectFlags(t) & ObjectFlags.ContainsWideningType) { if (!reportWideningErrorsInType(t)) { error(p.valueDeclaration, Diagnostics.Object_literal_s_property_0_implicitly_has_an_1_type, symbolToString(p), typeToString(getWidenedType(t))); } errorReported = true; } } } } return errorReported; } function reportImplicitAny(declaration: Declaration, type: Type, wideningKind?: WideningKind) { const typeAsString = typeToString(getWidenedType(type)); if (isInJSFile(declaration) && !isCheckJsEnabledForFile(getSourceFileOfNode(declaration), compilerOptions)) { // Only report implicit any errors/suggestions in TS and ts-check JS files return; } let diagnostic: DiagnosticMessage; switch (declaration.kind) { case SyntaxKind.BinaryExpression: case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: diagnostic = noImplicitAny ? Diagnostics.Member_0_implicitly_has_an_1_type : Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; break; case SyntaxKind.Parameter: const param = declaration as ParameterDeclaration; if (isIdentifier(param.name) && (isCallSignatureDeclaration(param.parent) || isMethodSignature(param.parent) || isFunctionTypeNode(param.parent)) && param.parent.parameters.indexOf(param) > -1 && (resolveName(param, param.name.escapedText, SymbolFlags.Type, undefined, param.name.escapedText, /*isUse*/ true) || param.name.originalKeywordKind && isTypeNodeKind(param.name.originalKeywordKind))) { const newName = "arg" + param.parent.parameters.indexOf(param); errorOrSuggestion(noImplicitAny, declaration, Diagnostics.Parameter_has_a_name_but_no_type_Did_you_mean_0_Colon_1, newName, declarationNameToString(param.name)); return; } diagnostic = (declaration).dotDotDotToken ? noImplicitAny ? Diagnostics.Rest_parameter_0_implicitly_has_an_any_type : Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage : noImplicitAny ? Diagnostics.Parameter_0_implicitly_has_an_1_type : Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; break; case SyntaxKind.BindingElement: diagnostic = Diagnostics.Binding_element_0_implicitly_has_an_1_type; if (!noImplicitAny) { // Don't issue a suggestion for binding elements since the codefix doesn't yet support them. return; } break; case SyntaxKind.JSDocFunctionType: error(declaration, Diagnostics.Function_type_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); return; case SyntaxKind.FunctionDeclaration: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: if (noImplicitAny && !(declaration as NamedDeclaration).name) { if (wideningKind === WideningKind.GeneratorYield) { error(declaration, Diagnostics.Generator_implicitly_has_yield_type_0_because_it_does_not_yield_any_values_Consider_supplying_a_return_type_annotation, typeAsString); } else { error(declaration, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString); } return; } diagnostic = !noImplicitAny ? Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage : wideningKind === WideningKind.GeneratorYield ? Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_yield_type : Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type; break; case SyntaxKind.MappedType: if (noImplicitAny) { error(declaration, Diagnostics.Mapped_object_type_implicitly_has_an_any_template_type); } return; default: diagnostic = noImplicitAny ? Diagnostics.Variable_0_implicitly_has_an_1_type : Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage; } errorOrSuggestion(noImplicitAny, declaration, diagnostic, declarationNameToString(getNameOfDeclaration(declaration)), typeAsString); } function reportErrorsFromWidening(declaration: Declaration, type: Type, wideningKind?: WideningKind) { if (produceDiagnostics && noImplicitAny && getObjectFlags(type) & ObjectFlags.ContainsWideningType && (!wideningKind || !getContextualSignatureForFunctionLikeDeclaration(declaration as FunctionLikeDeclaration))) { // Report implicit any error within type if possible, otherwise report error on declaration if (!reportWideningErrorsInType(type)) { reportImplicitAny(declaration, type, wideningKind); } } } function applyToParameterTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { const sourceCount = getParameterCount(source); const targetCount = getParameterCount(target); const sourceRestType = getEffectiveRestType(source); const targetRestType = getEffectiveRestType(target); const targetNonRestCount = targetRestType ? targetCount - 1 : targetCount; const paramCount = sourceRestType ? targetNonRestCount : Math.min(sourceCount, targetNonRestCount); const sourceThisType = getThisTypeOfSignature(source); if (sourceThisType) { const targetThisType = getThisTypeOfSignature(target); if (targetThisType) { callback(sourceThisType, targetThisType); } } for (let i = 0; i < paramCount; i++) { callback(getTypeAtPosition(source, i), getTypeAtPosition(target, i)); } if (targetRestType) { callback(getRestTypeAtPosition(source, paramCount), targetRestType); } } function applyToReturnTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) { const sourceTypePredicate = getTypePredicateOfSignature(source); const targetTypePredicate = getTypePredicateOfSignature(target); if (sourceTypePredicate && targetTypePredicate && typePredicateKindsMatch(sourceTypePredicate, targetTypePredicate) && sourceTypePredicate.type && targetTypePredicate.type) { callback(sourceTypePredicate.type, targetTypePredicate.type); } else { callback(getReturnTypeOfSignature(source), getReturnTypeOfSignature(target)); } } function createInferenceContext(typeParameters: readonly TypeParameter[], signature: Signature | undefined, flags: InferenceFlags, compareTypes?: TypeComparer): InferenceContext { return createInferenceContextWorker(typeParameters.map(createInferenceInfo), signature, flags, compareTypes || compareTypesAssignable); } function cloneInferenceContext(context: T, extraFlags: InferenceFlags = 0): InferenceContext | T & undefined { return context && createInferenceContextWorker(map(context.inferences, cloneInferenceInfo), context.signature, context.flags | extraFlags, context.compareTypes); } function createInferenceContextWorker(inferences: InferenceInfo[], signature: Signature | undefined, flags: InferenceFlags, compareTypes: TypeComparer): InferenceContext { const context: InferenceContext = { inferences, signature, flags, compareTypes, mapper: makeFunctionTypeMapper(t => mapToInferredType(context, t, /*fix*/ true)), nonFixingMapper: makeFunctionTypeMapper(t => mapToInferredType(context, t, /*fix*/ false)), }; return context; } function mapToInferredType(context: InferenceContext, t: Type, fix: boolean): Type { const inferences = context.inferences; for (let i = 0; i < inferences.length; i++) { const inference = inferences[i]; if (t === inference.typeParameter) { if (fix && !inference.isFixed) { clearCachedInferences(inferences); inference.isFixed = true; } return getInferredType(context, i); } } return t; } function clearCachedInferences(inferences: InferenceInfo[]) { for (const inference of inferences) { if (!inference.isFixed) { inference.inferredType = undefined; } } } function createInferenceInfo(typeParameter: TypeParameter): InferenceInfo { return { typeParameter, candidates: undefined, contraCandidates: undefined, inferredType: undefined, priority: undefined, topLevel: true, isFixed: false, impliedArity: undefined }; } function cloneInferenceInfo(inference: InferenceInfo): InferenceInfo { return { typeParameter: inference.typeParameter, candidates: inference.candidates && inference.candidates.slice(), contraCandidates: inference.contraCandidates && inference.contraCandidates.slice(), inferredType: inference.inferredType, priority: inference.priority, topLevel: inference.topLevel, isFixed: inference.isFixed, impliedArity: inference.impliedArity }; } function cloneInferredPartOfContext(context: InferenceContext): InferenceContext | undefined { const inferences = filter(context.inferences, hasInferenceCandidates); return inferences.length ? createInferenceContextWorker(map(inferences, cloneInferenceInfo), context.signature, context.flags, context.compareTypes) : undefined; } function getMapperFromContext(context: T): TypeMapper | T & undefined { return context && context.mapper; } // Return true if the given type could possibly reference a type parameter for which // we perform type inference (i.e. a type parameter of a generic function). We cache // results for union and intersection types for performance reasons. function couldContainTypeVariables(type: Type): boolean { const objectFlags = getObjectFlags(type); if (objectFlags & ObjectFlags.CouldContainTypeVariablesComputed) { return !!(objectFlags & ObjectFlags.CouldContainTypeVariables); } const result = !!(type.flags & TypeFlags.Instantiable || type.flags & TypeFlags.Object && !isNonGenericTopLevelType(type) && ( objectFlags & ObjectFlags.Reference && ((type).node || forEach(getTypeArguments(type), 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.ObjectRestType)) || type.flags & TypeFlags.UnionOrIntersection && !(type.flags & TypeFlags.EnumLiteral) && !isNonGenericTopLevelType(type) && some((type).types, couldContainTypeVariables)); if (type.flags & TypeFlags.ObjectFlagsType) { (type).objectFlags |= ObjectFlags.CouldContainTypeVariablesComputed | (result ? ObjectFlags.CouldContainTypeVariables : 0); } return result; } function isNonGenericTopLevelType(type: Type) { if (type.aliasSymbol && !type.aliasTypeArguments) { const declaration = getDeclarationOfKind(type.aliasSymbol, SyntaxKind.TypeAliasDeclaration); return !!(declaration && findAncestor(declaration.parent, n => n.kind === SyntaxKind.SourceFile ? true : n.kind === SyntaxKind.ModuleDeclaration ? false : "quit")); } return false; } function isTypeParameterAtTopLevel(type: Type, typeParameter: TypeParameter): boolean { return !!(type === typeParameter || type.flags & TypeFlags.UnionOrIntersection && some((type).types, t => isTypeParameterAtTopLevel(t, typeParameter)) || type.flags & TypeFlags.Conditional && (getTrueTypeFromConditionalType(type) === typeParameter || getFalseTypeFromConditionalType(type) === typeParameter)); } /** Create an object with properties named in the string literal type. Every property has type `any` */ function createEmptyObjectTypeFromStringLiteral(type: Type) { const members = createSymbolTable(); forEachType(type, t => { if (!(t.flags & TypeFlags.StringLiteral)) { return; } const name = escapeLeadingUnderscores((t as StringLiteralType).value); const literalProp = createSymbol(SymbolFlags.Property, name); literalProp.type = anyType; if (t.symbol) { literalProp.declarations = t.symbol.declarations; literalProp.valueDeclaration = t.symbol.valueDeclaration; } members.set(name, literalProp); }); const indexInfo = type.flags & TypeFlags.String ? createIndexInfo(emptyObjectType, /*isReadonly*/ false) : undefined; return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfo, undefined); } /** * Infer a suitable input type for a homomorphic mapped type { [P in keyof T]: X }. We construct * an object type with the same set of properties as the source type, where the type of each * property is computed by inferring from the source property type to X for the type * variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for). */ function inferTypeForHomomorphicMappedType(source: Type, target: MappedType, constraint: IndexType): Type | undefined { if (inInferTypeForHomomorphicMappedType) { return undefined; } const key = source.id + "," + target.id + "," + constraint.id; if (reverseMappedCache.has(key)) { return reverseMappedCache.get(key); } inInferTypeForHomomorphicMappedType = true; const type = createReverseMappedType(source, target, constraint); inInferTypeForHomomorphicMappedType = false; reverseMappedCache.set(key, type); return type; } // We consider a type to be partially inferable if it isn't marked non-inferable or if it is // an object literal type with at least one property of an inferable type. For example, an object // literal { a: 123, b: x => true } is marked non-inferable because it contains a context sensitive // arrow function, but is considered partially inferable because property 'a' has an inferable type. function isPartiallyInferableType(type: Type): boolean { return !(getObjectFlags(type) & ObjectFlags.NonInferrableType) || isObjectLiteralType(type) && some(getPropertiesOfType(type), prop => isPartiallyInferableType(getTypeOfSymbol(prop))) || isTupleType(type) && some(getTypeArguments(type), isPartiallyInferableType); } function createReverseMappedType(source: Type, target: MappedType, constraint: IndexType) { // We consider a source type reverse mappable if it has a string index signature or if // it has one or more properties and is of a partially inferable type. if (!(getIndexInfoOfType(source, IndexKind.String) || getPropertiesOfType(source).length !== 0 && isPartiallyInferableType(source))) { return undefined; } // For arrays and tuples we infer new arrays and tuples where the reverse mapping has been // applied to the element type(s). if (isArrayType(source)) { return createArrayType(inferReverseMappedType(getTypeArguments(source)[0], target, constraint), isReadonlyArrayType(source)); } if (isTupleType(source)) { const elementTypes = map(getTypeArguments(source), t => inferReverseMappedType(t, target, constraint)); const elementFlags = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ? sameMap(source.target.elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) : source.target.elementFlags; return createTupleType(elementTypes, elementFlags, source.target.readonly, source.target.labeledElementDeclarations); } // For all other object types we infer a new object type where the reverse mapping has been // applied to the type of each property. const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType; reversed.source = source; reversed.mappedType = target; reversed.constraintType = constraint; return reversed; } function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) { return inferReverseMappedType(symbol.propertyType, symbol.mappedType, symbol.constraintType); } function inferReverseMappedType(sourceType: Type, target: MappedType, constraint: IndexType): Type { const typeParameter = getIndexedAccessType(constraint.type, getTypeParameterFromMappedType(target)); const templateType = getTemplateTypeFromMappedType(target); const inference = createInferenceInfo(typeParameter); inferTypes([inference], sourceType, templateType); return getTypeFromInference(inference) || unknownType; } function* getUnmatchedProperties(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): IterableIterator { const properties = getPropertiesOfType(target); for (const targetProp of properties) { // TODO: remove this when we support static private identifier fields and find other solutions to get privateNamesAndStaticFields test to pass if (isStaticPrivateIdentifierProperty(targetProp)) { continue; } if (requireOptionalProperties || !(targetProp.flags & SymbolFlags.Optional || getCheckFlags(targetProp) & CheckFlags.Partial)) { const sourceProp = getPropertyOfType(source, targetProp.escapedName); if (!sourceProp) { yield targetProp; } else if (matchDiscriminantProperties) { const targetType = getTypeOfSymbol(targetProp); if (targetType.flags & TypeFlags.Unit) { const sourceType = getTypeOfSymbol(sourceProp); if (!(sourceType.flags & TypeFlags.Any || getRegularTypeOfLiteralType(sourceType) === getRegularTypeOfLiteralType(targetType))) { yield targetProp; } } } } } } function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean, matchDiscriminantProperties: boolean): Symbol | undefined { const result = getUnmatchedProperties(source, target, requireOptionalProperties, matchDiscriminantProperties).next(); if (!result.done) return result.value; } function tupleTypesDefinitelyUnrelated(source: TupleTypeReference, target: TupleTypeReference) { return !(target.target.combinedFlags & ElementFlags.Variadic) && target.target.minLength > source.target.minLength || !target.target.hasRestElement && (source.target.hasRestElement || target.target.fixedLength < source.target.fixedLength); } function typesDefinitelyUnrelated(source: Type, target: Type) { // Two tuple types with incompatible arities are definitely unrelated. // Two object types that each have a property that is unmatched in the other are definitely unrelated. return isTupleType(source) && isTupleType(target) ? tupleTypesDefinitelyUnrelated(source, target) : !!getUnmatchedProperty(source, target, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ true) && !!getUnmatchedProperty(target, source, /*requireOptionalProperties*/ false, /*matchDiscriminantProperties*/ true); } function getTypeFromInference(inference: InferenceInfo) { return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) : inference.contraCandidates ? getIntersectionType(inference.contraCandidates) : undefined; } function hasSkipDirectInferenceFlag(node: Node) { return !!getNodeLinks(node).skipDirectInference; } function isFromInferenceBlockedSource(type: Type) { return !!(type.symbol && some(type.symbol.declarations, hasSkipDirectInferenceFlag)); } function isValidBigIntString(s: string): boolean { const scanner = createScanner(ScriptTarget.ESNext, /*skipTrivia*/ false); let success = true; scanner.setOnError(() => success = false); scanner.setText(s + "n"); let result = scanner.scan(); if (result === SyntaxKind.MinusToken) { result = scanner.scan(); } const flags = scanner.getTokenFlags(); // validate that // * scanning proceeded without error // * a bigint can be scanned, and that when it is scanned, it is // * the full length of the input string (so the scanner is one character beyond the augmented input length) // * it does not contain a numeric seperator (the `BigInt` constructor does not accept a numeric seperator in its input) return success && result === SyntaxKind.BigIntLiteral && scanner.getTextPos() === (s.length + 1) && !(flags & TokenFlags.ContainsSeparator); } function isStringLiteralTypeValueParsableAsType(s: StringLiteralType, target: Type): boolean { if (target.flags & TypeFlags.Union) { return !!forEachType(target, t => isStringLiteralTypeValueParsableAsType(s, t)); } switch (target) { case stringType: return true; case numberType: return s.value !== "" && isFinite(+(s.value)); case bigintType: return s.value !== "" && isValidBigIntString(s.value); // the next 4 should be handled in `getTemplateLiteralType`, as they are all exactly one value, but are here for completeness, just in case // this function is ever used on types which don't come from template literal holes case trueType: return s.value === "true"; case falseType: return s.value === "false"; case undefinedType: return s.value === "undefined"; case nullType: return s.value === "null"; default: return !!(target.flags & TypeFlags.Any); } } function inferLiteralsFromTemplateLiteralType(source: StringLiteralType, target: TemplateLiteralType): StringLiteralType[] | undefined { const value = source.value; const texts = target.texts; const lastIndex = texts.length - 1; const startText = texts[0]; const endText = texts[lastIndex]; if (!(value.startsWith(startText) && value.slice(startText.length).endsWith(endText))) return undefined; const matches = []; const str = value.slice(startText.length, value.length - endText.length); let pos = 0; for (let i = 1; i < lastIndex; i++) { const delim = texts[i]; const delimPos = delim.length > 0 ? str.indexOf(delim, pos) : pos < str.length ? pos + 1 : -1; if (delimPos < 0) return undefined; matches.push(getLiteralType(str.slice(pos, delimPos))); pos = delimPos + delim.length; } matches.push(getLiteralType(str.slice(pos))); return matches; } function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, contravariant = false) { let bivariant = false; let propagationType: Type; let inferencePriority = InferencePriority.MaxValue; let allowComplexConstraintInference = true; let visited: ESMap; let sourceStack: object[]; let targetStack: object[]; let expandingFlags = ExpandingFlags.None; inferFromTypes(originalSource, originalTarget); function inferFromTypes(source: Type, target: Type): void { if (!couldContainTypeVariables(target)) { return; } if (source === wildcardType) { // We are inferring from an 'any' type. We want to infer this type for every type parameter // referenced in the target type, so we record it as the propagation type and infer from the // target to itself. Then, as we find candidates we substitute the propagation type. const savePropagationType = propagationType; propagationType = source; inferFromTypes(target, target); propagationType = savePropagationType; return; } if (source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) { // Source and target are types originating in the same generic type alias declaration. // Simply infer from source type arguments to target type arguments. inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol)); return; } if (source === target && source.flags & TypeFlags.UnionOrIntersection) { // When source and target are the same union or intersection type, just relate each constituent // type to itself. for (const t of (source).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).types : [source], (target).types, isTypeOrBaseIdenticalTo); // Next, infer between closely matching source and target constituents and remove // the matching types. Types closely match when they are instantiations of the same // object type or instantiations of the same type alias. const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy); if (targets.length === 0) { return; } target = getUnionType(targets); if (sources.length === 0) { // All source constituents have been matched and there is nothing further to infer from. // However, simply making no inferences is undesirable because it could ultimately mean // inferring a type parameter constraint. Instead, make a lower priority inference from // the full source to whatever remains in the target. For example, when inferring from // string to 'string | T', make a lower priority inference of string for T. inferWithPriority(source, target, InferencePriority.NakedTypeVariable); return; } source = getUnionType(sources); } else if (target.flags & TypeFlags.Intersection && some((target).types, t => !!getInferenceInfoForType(t) || (isGenericMappedType(t) && !!getInferenceInfoForType(getHomomorphicTypeVariable(t) || neverType)))) { // We reduce intersection types only when they contain naked type parameters. For example, when // inferring from 'string[] & { extra: any }' to 'string[] & T' we want to remove string[] and // infer { extra: any } for T. But when inferring to 'string[] & Iterable' we want to keep the // string[] on the source side and infer string for T. // Likewise, we consider a homomorphic mapped type constrainted to the target type parameter as similar to a "naked type variable" // in such scenarios. if (!(source.flags & TypeFlags.Union)) { // Infer between identically matching source and target constituents and remove the matching types. const [sources, targets] = inferFromMatchingTypes(source.flags & TypeFlags.Intersection ? (source).types : [source], (target).types, isTypeIdenticalTo); if (sources.length === 0 || targets.length === 0) { return; } source = getIntersectionType(sources); target = getIntersectionType(targets); } } else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { target = getActualTypeVariable(target); } if (target.flags & TypeFlags.TypeVariable) { // If target is a type parameter, make an inference, unless the source type contains // the anyFunctionType (the wildcard type that's used to avoid contextually typing functions). // Because the anyFunctionType is internal, it should not be exposed to the user by adding // it as an inference candidate. Hopefully, a better candidate will come along that does // not contain anyFunctionType when we come back to this argument for its second round // of inference. Also, we exclude inferences for silentNeverType (which is used as a wildcard // when constructing types from type parameters that had no inference candidates). if (getObjectFlags(source) & ObjectFlags.NonInferrableType || source === nonInferrableAnyType || source === silentNeverType || (priority & InferencePriority.ReturnType && (source === autoType || source === autoArrayType)) || isFromInferenceBlockedSource(source)) { return; } const inference = getInferenceInfoForType(target); if (inference) { if (!inference.isFixed) { if (inference.priority === undefined || priority < inference.priority) { inference.candidates = undefined; inference.contraCandidates = undefined; inference.topLevel = true; inference.priority = priority; } if (priority === inference.priority) { const candidate = propagationType || source; // We make contravariant inferences only if we are in a pure contravariant position, // i.e. only if we have not descended into a bivariant position. if (contravariant && !bivariant) { if (!contains(inference.contraCandidates, candidate)) { inference.contraCandidates = append(inference.contraCandidates, candidate); clearCachedInferences(inferences); } } else if (!contains(inference.candidates, candidate)) { inference.candidates = append(inference.candidates, candidate); clearCachedInferences(inferences); } } if (!(priority & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && inference.topLevel && !isTypeParameterAtTopLevel(originalTarget, target)) { inference.topLevel = false; clearCachedInferences(inferences); } } inferencePriority = Math.min(inferencePriority, priority); return; } else { // Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine const simplified = getSimplifiedType(target, /*writing*/ false); if (simplified !== target) { invokeOnce(source, simplified, inferFromTypes); } else if (target.flags & TypeFlags.IndexedAccess) { const indexType = getSimplifiedType((target as IndexedAccessType).indexType, /*writing*/ false); // Generally simplifications of instantiable indexes are avoided to keep relationship checking correct, however if our target is an access, we can consider // that key of that access to be "instantiated", since we're looking to find the infernce goal in any way we can. if (indexType.flags & TypeFlags.Instantiable) { const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType, /*writing*/ false), indexType, /*writing*/ false); if (simplified && simplified !== target) { invokeOnce(source, simplified, inferFromTypes); } } } } } if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( (source).target === (target).target || isArrayType(source) && isArrayType(target)) && !((source).node && (target).node)) { // If source and target are references to the same generic type, infer from type arguments inferFromTypeArguments(getTypeArguments(source), getTypeArguments(target), getVariances((source).target)); } else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) { contravariant = !contravariant; inferFromTypes((source).type, (target).type); contravariant = !contravariant; } else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) { const empty = createEmptyObjectTypeFromStringLiteral(source); contravariant = !contravariant; inferWithPriority(empty, (target as IndexType).type, InferencePriority.LiteralKeyof); contravariant = !contravariant; } else if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) { inferFromTypes((source).objectType, (target).objectType); inferFromTypes((source).indexType, (target).indexType); } else if (source.flags & TypeFlags.StringMapping && target.flags & TypeFlags.StringMapping) { if ((source).symbol === (target).symbol) { inferFromTypes((source).type, (target).type); } } else if (target.flags & TypeFlags.Conditional) { invokeOnce(source, target, inferToConditionalType); } else if (target.flags & TypeFlags.UnionOrIntersection) { inferToMultipleTypes(source, (target).types, target.flags); } else if (source.flags & TypeFlags.Union) { // Source is a union or intersection type, infer from each constituent type const sourceTypes = (source).types; for (const sourceType of sourceTypes) { inferFromTypes(sourceType, target); } } else if (target.flags & TypeFlags.TemplateLiteral) { inferToTemplateLiteralType(source, target); } else { source = getReducedType(source); if (!(priority & InferencePriority.NoConstraints && source.flags & (TypeFlags.Intersection | TypeFlags.Instantiable))) { const apparentSource = getApparentType(source); // getApparentType can return _any_ type, since an indexed access or conditional may simplify to any other type. // If that occurs and it doesn't simplify to an object or intersection, we'll need to restart `inferFromTypes` // with the simplified source. if (apparentSource !== source && allowComplexConstraintInference && !(apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection))) { // TODO: The `allowComplexConstraintInference` flag is a hack! This forbids inference from complex constraints within constraints! // This isn't required algorithmically, but rather is used to lower the memory burden caused by performing inference // that is _too good_ in projects with complicated constraints (eg, fp-ts). In such cases, if we did not limit ourselves // here, we might produce more valid inferences for types, causing us to do more checks and perform more instantiations // (in addition to the extra stack depth here) which, in turn, can push the already close process over its limit. // TL;DR: If we ever become generally more memory efficient (or our resource budget ever increases), we should just // remove this `allowComplexConstraintInference` flag. allowComplexConstraintInference = false; return inferFromTypes(apparentSource, target); } source = apparentSource; } if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) { invokeOnce(source, target, inferFromObjectTypes); } } if (source.flags & TypeFlags.Simplifiable) { const simplified = getSimplifiedType(source, contravariant); if (simplified !== source) { inferFromTypes(simplified, target); } } } function inferWithPriority(source: Type, target: Type, newPriority: InferencePriority) { const savePriority = priority; priority |= newPriority; inferFromTypes(source, target); priority = savePriority; } function invokeOnce(source: Type, target: Type, action: (source: Type, target: Type) => void) { const key = source.id + "," + target.id; const status = visited && visited.get(key); if (status !== undefined) { inferencePriority = Math.min(inferencePriority, status); return; } (visited || (visited = new Map())).set(key, InferencePriority.Circularity); const saveInferencePriority = inferencePriority; inferencePriority = InferencePriority.MaxValue; // We stop inferring and report a circularity if we encounter duplicate recursion identities on both // the source side and the target side. const saveExpandingFlags = expandingFlags; const sourceIdentity = getRecursionIdentity(source) || source; const targetIdentity = getRecursionIdentity(target) || target; if (sourceIdentity && contains(sourceStack, sourceIdentity)) expandingFlags |= ExpandingFlags.Source; if (targetIdentity && contains(targetStack, targetIdentity)) expandingFlags |= ExpandingFlags.Target; if (expandingFlags !== ExpandingFlags.Both) { if (sourceIdentity) (sourceStack || (sourceStack = [])).push(sourceIdentity); if (targetIdentity) (targetStack || (targetStack = [])).push(targetIdentity); action(source, target); if (targetIdentity) targetStack.pop(); if (sourceIdentity) sourceStack.pop(); } else { inferencePriority = InferencePriority.Circularity; } expandingFlags = saveExpandingFlags; visited.set(key, inferencePriority); inferencePriority = Math.min(inferencePriority, saveInferencePriority); } function inferFromMatchingTypes(sources: Type[], targets: Type[], matches: (s: Type, t: Type) => boolean): [Type[], Type[]] { let matchedSources: Type[] | undefined; let matchedTargets: Type[] | undefined; for (const t of targets) { for (const s of sources) { if (matches(s, t)) { inferFromTypes(s, t); matchedSources = appendIfUnique(matchedSources, s); matchedTargets = appendIfUnique(matchedTargets, t); } } } return [ matchedSources ? filter(sources, t => !contains(matchedSources, t)) : sources, matchedTargets ? filter(targets, t => !contains(matchedTargets, t)) : targets, ]; } function inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) { const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length; for (let i = 0; i < count; i++) { if (i < variances.length && (variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Contravariant) { inferFromContravariantTypes(sourceTypes[i], targetTypes[i]); } else { inferFromTypes(sourceTypes[i], targetTypes[i]); } } } function inferFromContravariantTypes(source: Type, target: Type) { if (strictFunctionTypes || priority & InferencePriority.AlwaysStrict) { contravariant = !contravariant; inferFromTypes(source, target); contravariant = !contravariant; } else { inferFromTypes(source, target); } } function getInferenceInfoForType(type: Type) { if (type.flags & TypeFlags.TypeVariable) { for (const inference of inferences) { if (type === inference.typeParameter) { return inference; } } } return undefined; } function getSingleTypeVariableFromIntersectionTypes(types: Type[]) { let typeVariable: Type | undefined; for (const type of types) { const t = type.flags & TypeFlags.Intersection && find((type).types, t => !!getInferenceInfoForType(t)); if (!t || typeVariable && t !== typeVariable) { return undefined; } typeVariable = t; } return typeVariable; } function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) { let typeVariableCount = 0; if (targetFlags & TypeFlags.Union) { let nakedTypeVariable: Type | undefined; const sources = source.flags & TypeFlags.Union ? (source).types : [source]; const matched = new Array(sources.length); let inferenceCircularity = false; // First infer to types that are not naked type variables. For each source type we // track whether inferences were made from that particular type to some target with // equal priority (i.e. of equal quality) to what we would infer for a naked type // parameter. for (const t of targets) { if (getInferenceInfoForType(t)) { nakedTypeVariable = t; typeVariableCount++; } else { for (let i = 0; i < sources.length; i++) { const saveInferencePriority = inferencePriority; inferencePriority = InferencePriority.MaxValue; inferFromTypes(sources[i], t); if (inferencePriority === priority) matched[i] = true; inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity; inferencePriority = Math.min(inferencePriority, saveInferencePriority); } } } if (typeVariableCount === 0) { // If every target is an intersection of types containing a single naked type variable, // make a lower priority inference to that type variable. This handles inferring from // 'A | B' to 'T & (X | Y)' where we want to infer 'A | B' for T. const intersectionTypeVariable = getSingleTypeVariableFromIntersectionTypes(targets); if (intersectionTypeVariable) { inferWithPriority(source, intersectionTypeVariable, InferencePriority.NakedTypeVariable); } return; } // If the target has a single naked type variable and no inference circularities were // encountered above (meaning we explored the types fully), create a union of the source // types from which no inferences have been made so far and infer from that union to the // naked type variable. if (typeVariableCount === 1 && !inferenceCircularity) { const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); if (unmatched.length) { inferFromTypes(getUnionType(unmatched), nakedTypeVariable!); return; } } } else { // We infer from types that are not naked type variables first so that inferences we // make from nested naked type variables and given slightly higher priority by virtue // of being first in the candidates array. for (const t of targets) { if (getInferenceInfoForType(t)) { typeVariableCount++; } else { inferFromTypes(source, t); } } } // Inferences directly to naked type variables are given lower priority as they are // less specific. For example, when inferring from Promise to T | Promise, // we want to infer string for T, not Promise | string. For intersection types // we only infer to single naked type variables. if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) { for (const t of targets) { if (getInferenceInfoForType(t)) { inferWithPriority(source, t, InferencePriority.NakedTypeVariable); } } } } function inferToMappedType(source: Type, target: MappedType, constraintType: Type): boolean { if (constraintType.flags & TypeFlags.Union) { let result = false; for (const type of (constraintType as UnionType).types) { result = inferToMappedType(source, target, type) || result; } return result; } if (constraintType.flags & TypeFlags.Index) { // We're inferring from some source type S to a homomorphic mapped type { [P in keyof T]: X }, // where T is a type variable. Use inferTypeForHomomorphicMappedType to infer a suitable source // type and then make a secondary inference from that type to T. We make a secondary inference // such that direct inferences to T get priority over inferences to Partial, for example. const inference = getInferenceInfoForType((constraintType).type); if (inference && !inference.isFixed && !isFromInferenceBlockedSource(source)) { const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType); if (inferredType) { // We assign a lower priority to inferences made from types containing non-inferrable // types because we may only have a partial result (i.e. we may have failed to make // reverse inferences for some properties). inferWithPriority(inferredType, inference.typeParameter, getObjectFlags(source) & ObjectFlags.NonInferrableType ? InferencePriority.PartialHomomorphicMappedType : InferencePriority.HomomorphicMappedType); } } return true; } if (constraintType.flags & TypeFlags.TypeParameter) { // We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type // parameter. First infer from 'keyof S' to K. inferWithPriority(getIndexType(source), constraintType, InferencePriority.MappedTypeConstraint); // If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X }, // where K extends keyof T, we make the same inferences as for a homomorphic mapped type // { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a // Pick. const extendedConstraint = getConstraintOfType(constraintType); if (extendedConstraint && inferToMappedType(source, target, extendedConstraint)) { return true; } // If no inferences can be made to K's constraint, infer from a union of the property types // in the source to the template type X. const propTypes = map(getPropertiesOfType(source), getTypeOfSymbol); const stringIndexType = getIndexTypeOfType(source, IndexKind.String); const numberIndexInfo = getNonEnumNumberIndexInfo(source); const numberIndexType = numberIndexInfo && numberIndexInfo.type; inferFromTypes(getUnionType(append(append(propTypes, stringIndexType), numberIndexType)), getTemplateTypeFromMappedType(target)); return true; } return false; } function inferToConditionalType(source: Type, target: ConditionalType) { if (source.flags & TypeFlags.Conditional) { inferFromTypes((source).checkType, target.checkType); inferFromTypes((source).extendsType, target.extendsType); inferFromTypes(getTrueTypeFromConditionalType(source), getTrueTypeFromConditionalType(target)); inferFromTypes(getFalseTypeFromConditionalType(source), getFalseTypeFromConditionalType(target)); } else { const savePriority = priority; priority |= contravariant ? InferencePriority.ContravariantConditional : 0; const targetTypes = [getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)]; inferToMultipleTypes(source, targetTypes, target.flags); priority = savePriority; } } function inferToTemplateLiteralType(source: Type, target: TemplateLiteralType) { const matches = source.flags & TypeFlags.StringLiteral ? inferLiteralsFromTemplateLiteralType(source, target) : source.flags & TypeFlags.TemplateLiteral && arraysEqual((source).texts, target.texts) ? (source).types : undefined; const types = target.types; for (let i = 0; i < types.length; i++) { inferFromTypes(matches ? matches[i] : neverType, types[i]); } } function inferFromObjectTypes(source: Type, target: Type) { if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( (source).target === (target).target || isArrayType(source) && isArrayType(target))) { // If source and target are references to the same generic type, infer from type arguments inferFromTypeArguments(getTypeArguments(source), getTypeArguments(target), getVariances((source).target)); return; } if (isGenericMappedType(source) && isGenericMappedType(target)) { // The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer // from S to T and from X to Y. inferFromTypes(getConstraintTypeFromMappedType(source), getConstraintTypeFromMappedType(target)); inferFromTypes(getTemplateTypeFromMappedType(source), getTemplateTypeFromMappedType(target)); const sourceNameType = getNameTypeFromMappedType(source); const targetNameType = getNameTypeFromMappedType(target); if (sourceNameType && targetNameType) inferFromTypes(sourceNameType, targetNameType); } if (getObjectFlags(target) & ObjectFlags.Mapped && !(target).declaration.nameType) { const constraintType = getConstraintTypeFromMappedType(target); if (inferToMappedType(source, target, constraintType)) { return; } } // Infer from the members of source and target only if the two types are possibly related if (!typesDefinitelyUnrelated(source, target)) { if (isArrayType(source) || isTupleType(source)) { if (isTupleType(target)) { const sourceArity = getTypeReferenceArity(source); const targetArity = getTypeReferenceArity(target); const elementTypes = getTypeArguments(target); const elementFlags = target.target.elementFlags; // When source and target are tuple types with the same structure (fixed, variadic, and rest are matched // to the same kind in each position), simply infer between the element types. if (isTupleType(source) && isTupleTypeStructureMatching(source, target)) { for (let i = 0; i < targetArity; i++) { inferFromTypes(getTypeArguments(source)[i], elementTypes[i]); } return; } const startLength = isTupleType(source) ? Math.min(source.target.fixedLength, target.target.fixedLength) : 0; const sourceRestType = !isTupleType(source) || sourceArity > 0 && source.target.elementFlags[sourceArity - 1] & ElementFlags.Rest ? getTypeArguments(source)[sourceArity - 1] : undefined; const endLength = !(target.target.combinedFlags & ElementFlags.Variable) ? 0 : sourceRestType ? getEndLengthOfType(target) : Math.min(getEndLengthOfType(source), getEndLengthOfType(target)); const sourceEndLength = sourceRestType ? 0 : endLength; // Infer between starting fixed elements. for (let i = 0; i < startLength; i++) { inferFromTypes(getTypeArguments(source)[i], elementTypes[i]); } if (sourceRestType && sourceArity - startLength === 1) { // Single rest element remains in source, infer from that to every element in target for (let i = startLength; i < targetArity - endLength; i++) { inferFromTypes(elementFlags[i] & ElementFlags.Variadic ? createArrayType(sourceRestType) : sourceRestType, elementTypes[i]); } } else { const middleLength = targetArity - startLength - endLength; if (middleLength === 2 && elementFlags[startLength] & elementFlags[startLength + 1] & ElementFlags.Variadic && isTupleType(source)) { // Middle of target is [...T, ...U] and source is tuple type const targetInfo = getInferenceInfoForType(elementTypes[startLength]); if (targetInfo && targetInfo.impliedArity !== undefined) { // Infer slices from source based on implied arity of T. inferFromTypes(sliceTupleType(source, startLength, sourceEndLength + sourceArity - targetInfo.impliedArity), elementTypes[startLength]); inferFromTypes(sliceTupleType(source, startLength + targetInfo.impliedArity, sourceEndLength), elementTypes[startLength + 1]); } } else if (middleLength === 1 && elementFlags[startLength] & ElementFlags.Variadic) { // Middle of target is exactly one variadic element. Infer the slice between the fixed parts in the source. // If target ends in optional element(s), make a lower priority a speculative inference. const endsInOptional = target.target.elementFlags[targetArity - 1] & ElementFlags.Optional; const sourceSlice = isTupleType(source) ? sliceTupleType(source, startLength, sourceEndLength) : createArrayType(sourceRestType!); inferWithPriority(sourceSlice, elementTypes[startLength], endsInOptional ? InferencePriority.SpeculativeTuple : 0); } else if (middleLength === 1 && elementFlags[startLength] & ElementFlags.Rest) { // Middle of target is exactly one rest element. If middle of source is not empty, infer union of middle element types. const restType = isTupleType(source) ? getElementTypeOfSliceOfTupleType(source, startLength, sourceEndLength) : sourceRestType; if (restType) { inferFromTypes(restType, elementTypes[startLength]); } } } // Infer between ending fixed elements for (let i = 0; i < endLength; i++) { inferFromTypes(sourceRestType || getTypeArguments(source)[sourceArity - i - 1], elementTypes[targetArity - i - 1]); } return; } if (isArrayType(target)) { inferFromIndexTypes(source, target); return; } } inferFromProperties(source, target); inferFromSignatures(source, target, SignatureKind.Call); inferFromSignatures(source, target, SignatureKind.Construct); inferFromIndexTypes(source, target); } } function inferFromProperties(source: Type, target: Type) { const properties = getPropertiesOfObjectType(target); for (const targetProp of properties) { const sourceProp = getPropertyOfType(source, targetProp.escapedName); if (sourceProp) { inferFromTypes(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp)); } } } function inferFromSignatures(source: Type, target: Type, kind: SignatureKind) { const sourceSignatures = getSignaturesOfType(source, kind); const targetSignatures = getSignaturesOfType(target, kind); const sourceLen = sourceSignatures.length; const targetLen = targetSignatures.length; const len = sourceLen < targetLen ? sourceLen : targetLen; const skipParameters = !!(getObjectFlags(source) & ObjectFlags.NonInferrableType); for (let i = 0; i < len; i++) { inferFromSignature(getBaseSignature(sourceSignatures[sourceLen - len + i]), getErasedSignature(targetSignatures[targetLen - len + i]), skipParameters); } } function inferFromSignature(source: Signature, target: Signature, skipParameters: boolean) { if (!skipParameters) { const saveBivariant = bivariant; const kind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown; // Once we descend into a bivariant signature we remain bivariant for all nested inferences bivariant = bivariant || kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.MethodSignature || kind === SyntaxKind.Constructor; applyToParameterTypes(source, target, inferFromContravariantTypes); bivariant = saveBivariant; } applyToReturnTypes(source, target, inferFromTypes); } function inferFromIndexTypes(source: Type, target: Type) { const targetStringIndexType = getIndexTypeOfType(target, IndexKind.String); if (targetStringIndexType) { const sourceIndexType = getIndexTypeOfType(source, IndexKind.String) || getImplicitIndexTypeOfType(source, IndexKind.String); if (sourceIndexType) { inferFromTypes(sourceIndexType, targetStringIndexType); } } const targetNumberIndexType = getIndexTypeOfType(target, IndexKind.Number); if (targetNumberIndexType) { const sourceIndexType = getIndexTypeOfType(source, IndexKind.Number) || getIndexTypeOfType(source, IndexKind.String) || getImplicitIndexTypeOfType(source, IndexKind.Number); if (sourceIndexType) { inferFromTypes(sourceIndexType, targetNumberIndexType); } } } } function isTypeOrBaseIdenticalTo(s: Type, t: Type) { return isTypeIdenticalTo(s, t) || !!(t.flags & TypeFlags.String && s.flags & TypeFlags.StringLiteral || t.flags & TypeFlags.Number && s.flags & TypeFlags.NumberLiteral); } function isTypeCloselyMatchedBy(s: Type, t: Type) { return !!(s.flags & TypeFlags.Object && t.flags & TypeFlags.Object && s.symbol && s.symbol === t.symbol || s.aliasSymbol && s.aliasTypeArguments && s.aliasSymbol === t.aliasSymbol); } function hasPrimitiveConstraint(type: TypeParameter): boolean { const constraint = getConstraintOfTypeParameter(type); return !!constraint && maybeTypeOfKind(constraint.flags & TypeFlags.Conditional ? getDefaultConstraintOfConditionalType(constraint as ConditionalType) : constraint, TypeFlags.Primitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping); } function isObjectLiteralType(type: Type) { return !!(getObjectFlags(type) & ObjectFlags.ObjectLiteral); } function isObjectOrArrayLiteralType(type: Type) { return !!(getObjectFlags(type) & (ObjectFlags.ObjectLiteral | ObjectFlags.ArrayLiteral)); } function unionObjectAndArrayLiteralCandidates(candidates: Type[]): Type[] { if (candidates.length > 1) { const objectLiterals = filter(candidates, isObjectOrArrayLiteralType); if (objectLiterals.length) { const literalsType = getUnionType(objectLiterals, UnionReduction.Subtype); return concatenate(filter(candidates, t => !isObjectOrArrayLiteralType(t)), [literalsType]); } } return candidates; } function getContravariantInference(inference: InferenceInfo) { return inference.priority! & InferencePriority.PriorityImpliesCombination ? getIntersectionType(inference.contraCandidates!) : getCommonSubtype(inference.contraCandidates!); } function getCovariantInference(inference: InferenceInfo, signature: Signature) { // Extract all object and array literal types and replace them with a single widened and normalized type. const candidates = unionObjectAndArrayLiteralCandidates(inference.candidates!); // We widen inferred literal types if // all inferences were made to top-level occurrences of the type parameter, and // the type parameter has no constraint or its constraint includes no primitive or literal types, and // the type parameter was fixed during inference or does not occur at top-level in the return type. const primitiveConstraint = hasPrimitiveConstraint(inference.typeParameter); const widenLiteralTypes = !primitiveConstraint && inference.topLevel && (inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), inference.typeParameter)); const baseCandidates = primitiveConstraint ? sameMap(candidates, getRegularTypeOfLiteralType) : widenLiteralTypes ? sameMap(candidates, getWidenedLiteralType) : candidates; // If all inferences were made from a position that implies a combined result, infer a union type. // Otherwise, infer a common supertype. const unwidenedType = inference.priority! & InferencePriority.PriorityImpliesCombination ? getUnionType(baseCandidates, UnionReduction.Subtype) : getCommonSupertype(baseCandidates); return getWidenedType(unwidenedType); } function getInferredType(context: InferenceContext, index: number): Type { const inference = context.inferences[index]; if (!inference.inferredType) { let inferredType: Type | undefined; const signature = context.signature; if (signature) { const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined; if (inference.contraCandidates) { const inferredContravariantType = getContravariantInference(inference); // If we have both co- and contra-variant inferences, we prefer the contra-variant inference // unless the co-variant inference is a subtype and not 'never'. inferredType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) && isTypeSubtypeOf(inferredCovariantType, inferredContravariantType) ? inferredCovariantType : inferredContravariantType; } else if (inferredCovariantType) { inferredType = inferredCovariantType; } else if (context.flags & InferenceFlags.NoDefault) { // We use silentNeverType as the wildcard that signals no inferences. inferredType = silentNeverType; } else { // Infer either the default or the empty object type when no inferences were // made. It is important to remember that in this case, inference still // succeeds, meaning there is no error for not having inference candidates. An // inference error only occurs when there are *conflicting* candidates, i.e. // candidates with no common supertype. const defaultType = getDefaultFromTypeParameter(inference.typeParameter); if (defaultType) { // Instantiate the default type. Any forward reference to a type // parameter should be instantiated to the empty object type. inferredType = instantiateType(defaultType, mergeTypeMappers(createBackreferenceMapper(context, index), context.nonFixingMapper)); } } } else { inferredType = getTypeFromInference(inference); } inference.inferredType = inferredType || getDefaultTypeArgumentType(!!(context.flags & InferenceFlags.AnyDefault)); const constraint = getConstraintOfTypeParameter(inference.typeParameter); if (constraint) { const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper); if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) { inference.inferredType = inferredType = instantiatedConstraint; } } } return inference.inferredType; } function getDefaultTypeArgumentType(isInJavaScriptFile: boolean): Type { return isInJavaScriptFile ? anyType : unknownType; } function getInferredTypes(context: InferenceContext): Type[] { const result: Type[] = []; for (let i = 0; i < context.inferences.length; i++) { result.push(getInferredType(context, i)); } return result; } // EXPRESSION TYPE CHECKING function getCannotFindNameDiagnosticForName(node: Identifier): DiagnosticMessage { switch (node.escapedText) { case "document": case "console": return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_include_dom; case "$": return compilerOptions.types ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slashjquery_and_then_add_jquery_to_the_types_field_in_your_tsconfig : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_jQuery_Try_npm_i_save_dev_types_Slashjquery; case "describe": case "suite": case "it": case "test": return compilerOptions.types ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_save_dev_types_Slashjest_or_npm_i_save_dev_types_Slashmocha_and_then_add_jest_or_mocha_to_the_types_field_in_your_tsconfig : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_a_test_runner_Try_npm_i_save_dev_types_Slashjest_or_npm_i_save_dev_types_Slashmocha; case "process": case "require": case "Buffer": case "module": return compilerOptions.types ? Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashnode_and_then_add_node_to_the_types_field_in_your_tsconfig : Diagnostics.Cannot_find_name_0_Do_you_need_to_install_type_definitions_for_node_Try_npm_i_save_dev_types_Slashnode; case "Map": case "Set": case "Promise": case "Symbol": case "WeakMap": case "WeakSet": case "Iterator": case "AsyncIterator": case "SharedArrayBuffer": case "Atomics": case "AsyncIterable": case "AsyncIterableIterator": case "AsyncGenerator": case "AsyncGeneratorFunction": case "BigInt": case "Reflect": case "BigInt64Array": case "BigUint64Array": return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_1_or_later; default: if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { return Diagnostics.No_value_exists_in_scope_for_the_shorthand_property_0_Either_declare_one_or_provide_an_initializer; } else { return Diagnostics.Cannot_find_name_0; } } } function getResolvedSymbol(node: Identifier): Symbol { const links = getNodeLinks(node); if (!links.resolvedSymbol) { links.resolvedSymbol = !nodeIsMissing(node) && resolveName( node, node.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue, getCannotFindNameDiagnosticForName(node), node, !isWriteOnlyAccess(node), /*excludeGlobals*/ false, Diagnostics.Cannot_find_name_0_Did_you_mean_1) || unknownSymbol; } return links.resolvedSymbol; } function isInTypeQuery(node: Node): boolean { // TypeScript 1.0 spec (April 2014): 3.6.3 // A type query consists of the keyword typeof followed by an expression. // The expression is restricted to a single identifier or a sequence of identifiers separated by periods return !!findAncestor( node, n => n.kind === SyntaxKind.TypeQuery ? true : n.kind === SyntaxKind.Identifier || n.kind === SyntaxKind.QualifiedName ? false : "quit"); } // Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers // separated by dots). The key consists of the id of the symbol referenced by the // leftmost identifier followed by zero or more property names separated by dots. // The result is undefined if the reference isn't a dotted name. We prefix nodes // occurring in an apparent type position with '@' because the control flow type // of such nodes may be based on the apparent type instead of the declared type. function getFlowCacheKey(node: Node, declaredType: Type, initialType: Type, flowContainer: Node | undefined): string | undefined { switch (node.kind) { case SyntaxKind.Identifier: const symbol = getResolvedSymbol(node); return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${isConstraintPosition(node) ? "@" : ""}${getSymbolId(symbol)}` : undefined; case SyntaxKind.ThisKeyword: return `0|${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}`; case SyntaxKind.NonNullExpression: case SyntaxKind.ParenthesizedExpression: return getFlowCacheKey((node).expression, declaredType, initialType, flowContainer); case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: const propName = getAccessedPropertyName(node); if (propName !== undefined) { const key = getFlowCacheKey((node).expression, declaredType, initialType, flowContainer); return key && key + "." + propName; } } return undefined; } function isMatchingReference(source: Node, target: Node): boolean { switch (target.kind) { case SyntaxKind.ParenthesizedExpression: case SyntaxKind.NonNullExpression: return isMatchingReference(source, (target as NonNullExpression | ParenthesizedExpression).expression); case SyntaxKind.BinaryExpression: return (isAssignmentExpression(target) && isMatchingReference(source, target.left)) || (isBinaryExpression(target) && target.operatorToken.kind === SyntaxKind.CommaToken && isMatchingReference(source, target.right)); } switch (source.kind) { case SyntaxKind.Identifier: case SyntaxKind.PrivateIdentifier: return target.kind === SyntaxKind.Identifier && getResolvedSymbol(source) === getResolvedSymbol(target) || (target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) && getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source)) === getSymbolOfNode(target); case SyntaxKind.ThisKeyword: return target.kind === SyntaxKind.ThisKeyword; case SyntaxKind.SuperKeyword: return target.kind === SyntaxKind.SuperKeyword; case SyntaxKind.NonNullExpression: case SyntaxKind.ParenthesizedExpression: return isMatchingReference((source as NonNullExpression | ParenthesizedExpression).expression, target); case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: return isAccessExpression(target) && getAccessedPropertyName(source) === getAccessedPropertyName(target) && isMatchingReference((source).expression, target.expression); } return false; } // Given a source x, check if target matches x or is an && operation with an operand that matches x. function containsTruthyCheck(source: Node, target: Node): boolean { return isMatchingReference(source, target) || (target.kind === SyntaxKind.BinaryExpression && (target).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && (containsTruthyCheck(source, (target).left) || containsTruthyCheck(source, (target).right))); } function getAccessedPropertyName(access: AccessExpression): __String | undefined { return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText : isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) : 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, name); if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) { if ((prop).isDiscriminantProperty === undefined) { (prop).isDiscriminantProperty = ((prop).checkFlags & CheckFlags.Discriminant) === CheckFlags.Discriminant && !maybeTypeOfKind(getTypeOfSymbol(prop), TypeFlags.Instantiable); } return !!(prop).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; } function isOrContainsMatchingReference(source: Node, target: Node) { return isMatchingReference(source, target) || containsMatchingReference(source, target); } function hasMatchingArgument(callExpression: CallExpression, reference: Node) { if (callExpression.arguments) { for (const argument of callExpression.arguments) { if (isOrContainsMatchingReference(reference, argument)) { return true; } } } if (callExpression.expression.kind === SyntaxKind.PropertyAccessExpression && isOrContainsMatchingReference(reference, (callExpression.expression).expression)) { return true; } return false; } function getFlowNodeId(flow: FlowNode): number { if (!flow.id || flow.id < 0) { flow.id = nextFlowId; nextFlowId++; } return flow.id; } function typeMaybeAssignableTo(source: Type, target: Type) { if (!(source.flags & TypeFlags.Union)) { return isTypeAssignableTo(source, target); } for (const t of (source).types) { if (isTypeAssignableTo(t, target)) { return true; } } return false; } // Remove those constituent types of declaredType to which no constituent type of assignedType is assignable. // For example, when a variable of type number | string | boolean is assigned a value of type number | boolean, // we remove type string. function getAssignmentReducedType(declaredType: UnionType, assignedType: Type) { if (declaredType !== assignedType) { if (assignedType.flags & TypeFlags.Never) { return assignedType; } let reducedType = filterType(declaredType, t => typeMaybeAssignableTo(assignedType, t)); if (assignedType.flags & TypeFlags.BooleanLiteral && isFreshLiteralType(assignedType)) { reducedType = mapType(reducedType, getFreshTypeOfLiteralType); // Ensure that if the assignment is a fresh type, that we narrow to fresh types } // Our crude heuristic produces an invalid result in some cases: see GH#26130. // For now, when that happens, we give up and don't narrow at all. (This also // means we'll never narrow for erroneous assignments where the assigned type // is not assignable to the declared type.) if (isTypeAssignableTo(assignedType, reducedType)) { return reducedType; } } return declaredType; } function getTypeFactsOfTypes(types: Type[]): TypeFacts { let result: TypeFacts = TypeFacts.None; for (const t of types) { result |= getTypeFacts(t); } return result; } function isFunctionObjectType(type: ObjectType): boolean { // We do a quick check for a "bind" property before performing the more expensive subtype // check. This gives us a quicker out in the common case where an object type is not a function. const resolved = resolveStructuredTypeMembers(type); return !!(resolved.callSignatures.length || resolved.constructSignatures.length || resolved.members.get("bind" as __String) && isTypeSubtypeOf(type, globalFunctionType)); } function getTypeFacts(type: Type): TypeFacts { const flags = type.flags; if (flags & TypeFlags.String) { return strictNullChecks ? TypeFacts.StringStrictFacts : TypeFacts.StringFacts; } if (flags & TypeFlags.StringLiteral) { const isEmpty = (type).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).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); 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) { return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type) ? strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts : isFunctionObjectType(type) ? strictNullChecks ? TypeFacts.FunctionStrictFacts : TypeFacts.FunctionFacts : strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; } if (flags & (TypeFlags.Void | TypeFlags.Undefined)) { return TypeFacts.UndefinedFacts; } if (flags & TypeFlags.Null) { return TypeFacts.NullFacts; } if (flags & TypeFlags.ESSymbolLike) { return strictNullChecks ? TypeFacts.SymbolStrictFacts : TypeFacts.SymbolFacts; } if (flags & TypeFlags.NonPrimitive) { return strictNullChecks ? TypeFacts.ObjectStrictFacts : TypeFacts.ObjectFacts; } if (flags & TypeFlags.Never) { return TypeFacts.None; } if (flags & TypeFlags.Instantiable) { return getTypeFacts(getBaseConstraintOfType(type) || unknownType); } if (flags & TypeFlags.UnionOrIntersection) { return getTypeFactsOfTypes((type).types); } return TypeFacts.All; } function getTypeWithFacts(type: Type, include: TypeFacts) { return filterType(type, t => (getTypeFacts(t) & include) !== 0); } function getTypeWithDefault(type: Type, defaultExpression: Expression) { if (defaultExpression) { const defaultType = getTypeOfExpression(defaultExpression); return getUnionType([getTypeWithFacts(type, TypeFacts.NEUndefined), defaultType]); } return type; } function getTypeOfDestructuredProperty(type: Type, name: PropertyName) { const nameType = getLiteralTypeFromPropertyName(name); if (!isTypeUsableAsPropertyName(nameType)) return errorType; const text = getPropertyNameFromType(nameType); return getConstraintForLocation(getTypeOfPropertyOfType(type, text), name) || isNumericLiteralName(text) && includeUndefinedInIndexSignature(getIndexTypeOfType(type, IndexKind.Number)) || includeUndefinedInIndexSignature(getIndexTypeOfType(type, IndexKind.String)) || errorType; } function getTypeOfDestructuredArrayElement(type: Type, index: number) { return everyType(type, isTupleLikeType) && getTupleElementType(type, index) || includeUndefinedInIndexSignature(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined)) || errorType; } function includeUndefinedInIndexSignature(type: Type | undefined): Type | undefined { if (!type) return type; return compilerOptions.noUncheckedIndexedAccess ? getUnionType([type, undefinedType]) : type; } function getTypeOfDestructuredSpreadExpression(type: Type) { return createArrayType(checkIteratedTypeOrElementType(IterationUse.Destructuring, type, undefinedType, /*errorNode*/ undefined) || errorType); } function getAssignedTypeOfBinaryExpression(node: BinaryExpression): Type { const isDestructuringDefaultAssignment = node.parent.kind === SyntaxKind.ArrayLiteralExpression && isDestructuringAssignmentTarget(node.parent) || node.parent.kind === SyntaxKind.PropertyAssignment && isDestructuringAssignmentTarget(node.parent.parent); return isDestructuringDefaultAssignment ? getTypeWithDefault(getAssignedType(node), node.right) : getTypeOfExpression(node.right); } function isDestructuringAssignmentTarget(parent: Node) { return parent.parent.kind === SyntaxKind.BinaryExpression && (parent.parent as BinaryExpression).left === parent || parent.parent.kind === SyntaxKind.ForOfStatement && (parent.parent as ForOfStatement).initializer === parent; } function getAssignedTypeOfArrayLiteralElement(node: ArrayLiteralExpression, element: Expression): Type { return getTypeOfDestructuredArrayElement(getAssignedType(node), node.elements.indexOf(element)); } function getAssignedTypeOfSpreadExpression(node: SpreadElement): Type { return getTypeOfDestructuredSpreadExpression(getAssignedType(node.parent)); } 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) || errorType; case SyntaxKind.BinaryExpression: return getAssignedTypeOfBinaryExpression(parent); case SyntaxKind.DeleteExpression: return undefinedType; case SyntaxKind.ArrayLiteralExpression: return getAssignedTypeOfArrayLiteralElement(parent, node); case SyntaxKind.SpreadElement: return getAssignedTypeOfSpreadExpression(parent); case SyntaxKind.PropertyAssignment: return getAssignedTypeOfPropertyAssignment(parent); case SyntaxKind.ShorthandPropertyAssignment: return getAssignedTypeOfShorthandPropertyAssignment(parent); } return errorType; } function getInitialTypeOfBindingElement(node: BindingElement): Type { const pattern = node.parent; const parentType = getInitialType(pattern.parent); const type = pattern.kind === SyntaxKind.ObjectBindingPattern ? getTypeOfDestructuredProperty(parentType, node.propertyName || node.name) : !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).initializer && isEmptyArrayLiteral((node).initializer!) || node.kind !== SyntaxKind.BindingElement && node.parent.kind === SyntaxKind.BinaryExpression && isEmptyArrayLiteral((node.parent).right); } function getReferenceCandidate(node: Expression): Expression { switch (node.kind) { case SyntaxKind.ParenthesizedExpression: return getReferenceCandidate((node).expression); case SyntaxKind.BinaryExpression: switch ((node).operatorToken.kind) { case SyntaxKind.EqualsToken: case SyntaxKind.BarBarEqualsToken: case SyntaxKind.AmpersandAmpersandEqualsToken: case SyntaxKind.QuestionQuestionEqualsToken: return getReferenceCandidate((node).left); case SyntaxKind.CommaToken: return getReferenceCandidate((node).right); } } return node; } function getReferenceRoot(node: Node): Node { const { parent } = node; return parent.kind === SyntaxKind.ParenthesizedExpression || parent.kind === SyntaxKind.BinaryExpression && (parent).operatorToken.kind === SyntaxKind.EqualsToken && (parent).left === node || parent.kind === SyntaxKind.BinaryExpression && (parent).operatorToken.kind === SyntaxKind.CommaToken && (parent).right === node ? getReferenceRoot(parent) : node; } function getTypeOfSwitchClause(clause: CaseClause | DefaultClause) { if (clause.kind === SyntaxKind.CaseClause) { return getRegularTypeOfLiteralType(getTypeOfExpression(clause.expression)); } return neverType; } function getSwitchClauseTypes(switchStatement: SwitchStatement): Type[] { const links = getNodeLinks(switchStatement); if (!links.switchTypes) { links.switchTypes = []; for (const clause of switchStatement.caseBlock.clauses) { links.switchTypes.push(getTypeOfSwitchClause(clause)); } } return links.switchTypes; } // Get the types from all cases in a switch on `typeof`. An // `undefined` element denotes an explicit `default` clause. function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: false): string[]; function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: boolean): (string | undefined)[]; function getSwitchClauseTypeOfWitnesses(switchStatement: SwitchStatement, retainDefault: boolean): (string | undefined)[] { const witnesses: (string | undefined)[] = []; for (const clause of switchStatement.caseBlock.clauses) { if (clause.kind === SyntaxKind.CaseClause) { if (isStringLiteralLike(clause.expression)) { witnesses.push(clause.expression.text); continue; } return emptyArray; } if (retainDefault) witnesses.push(/*explicitDefaultStatement*/ undefined); } return witnesses; } function eachTypeContainedIn(source: Type, types: Type[]) { return source.flags & TypeFlags.Union ? !forEach((source).types, t => !contains(types, t)) : contains(types, source); } function isTypeSubsetOf(source: Type, target: Type) { return source === target || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, target); } function isTypeSubsetOfUnion(source: Type, target: UnionType) { if (source.flags & TypeFlags.Union) { for (const t of (source).types) { if (!containsType(target.types, t)) { return false; } } return true; } if (source.flags & TypeFlags.EnumLiteral && getBaseTypeOfEnumLiteralType(source) === 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).types, f) : f(type); } function everyType(type: Type, f: (t: Type) => boolean): boolean { return type.flags & TypeFlags.Union ? every((type).types, f) : f(type); } function filterType(type: Type, f: (t: Type) => boolean): Type { if (type.flags & TypeFlags.Union) { const types = (type).types; const filtered = filter(types, f); return filtered === types ? type : getUnionTypeFromSortedList(filtered, (type).objectFlags); } return type.flags & TypeFlags.Never || f(type) ? type : neverType; } 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); } let mappedTypes: Type[] | undefined; for (const t of (type).types) { const mapped = mapper(t); if (mapped) { if (!mappedTypes) { mappedTypes = [mapped]; } else { mappedTypes.push(mapped); } } } return mappedTypes && getUnionType(mappedTypes, noReductions ? UnionReduction.None : UnionReduction.Literal); } function extractTypesOfKind(type: Type, kind: TypeFlags) { return filterType(type, t => (t.flags & kind) !== 0); } // Return a new type in which occurrences of the string and number primitive types in // typeWithPrimitives have been replaced with occurrences of string literals and numeric // literals in typeWithLiterals, respectively. function replacePrimitivesWithLiterals(typeWithPrimitives: Type, typeWithLiterals: Type) { if (isTypeSubsetOf(stringType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.StringLiteral) || isTypeSubsetOf(numberType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.NumberLiteral) || isTypeSubsetOf(bigintType, typeWithPrimitives) && maybeTypeOfKind(typeWithLiterals, TypeFlags.BigIntLiteral)) { return mapType(typeWithPrimitives, t => t.flags & TypeFlags.String ? extractTypesOfKind(typeWithLiterals, TypeFlags.String | 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; } 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); 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).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) : type; } function getElementTypeOfEvolvingArrayType(type: Type) { return getObjectFlags(type) & ObjectFlags.EvolvingArray ? (type).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; } // At flow control branch or loop junctions, if the type along every antecedent code path // is an evolving array type, we construct a combined evolving array type. Otherwise we // finalize all evolving array types. function getUnionOrEvolvingArrayType(types: Type[], subtypeReduction: UnionReduction) { return isEvolvingArrayTypeList(types) ? getEvolvingArrayType(getUnionType(map(types, getElementTypeOfEvolvingArrayType))) : getUnionType(sameMap(types, finalizeEvolvingArrayType), subtypeReduction); } // 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).expression === root && parent.parent.kind === SyntaxKind.BinaryExpression && (parent.parent).operatorToken.kind === SyntaxKind.EqualsToken && (parent.parent).left === parent && !isAssignmentTarget(parent.parent) && isTypeAssignableToKind(getTypeOfExpression((parent).argumentExpression), TypeFlags.NumberLike); return isLengthPushOrUnshift || isElementAssignment; } function isDeclarationWithExplicitTypeAnnotation(declaration: Declaration) { return (declaration.kind === SyntaxKind.VariableDeclaration || declaration.kind === SyntaxKind.Parameter || declaration.kind === SyntaxKind.PropertyDeclaration || declaration.kind === SyntaxKind.PropertySignature) && !!getEffectiveTypeAnnotationNode(declaration as VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature); } function getExplicitTypeOfSymbol(symbol: Symbol, diagnostic?: Diagnostic) { if (symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.ValueModule)) { return getTypeOfSymbol(symbol); } if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { if (getCheckFlags(symbol) & CheckFlags.Mapped) { const origin = (symbol).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)); return getExplicitTypeOfSymbol(symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol, diagnostic); case SyntaxKind.ThisKeyword: return getExplicitThisType(node); case SyntaxKind.SuperKeyword: return checkSuperExpression(node); case SyntaxKind.PropertyAccessExpression: const type = getTypeOfDottedName((node).expression, diagnostic); const prop = type && getPropertyOfType(type, (node).name.escapedText); return prop && getExplicitTypeOfSymbol(prop, diagnostic); case SyntaxKind.ParenthesizedExpression: return getTypeOfDottedName((node).expression, diagnostic); } } } function getEffectsSignature(node: CallExpression) { const links = getNodeLinks(node); let signature = links.effectsSignature; if (signature === undefined) { // A call expression parented by an expression statement is a potential assertion. Other call // expressions are potential type predicate function calls. In order to avoid triggering // circularities in control flow analysis, we use getTypeOfDottedName when resolving the call // target expression of an assertion. let funcType: Type | undefined; if (node.parent.kind === SyntaxKind.ExpressionStatement) { funcType = getTypeOfDottedName(node.expression, /*diagnostic*/ undefined); } else if (node.expression.kind !== SyntaxKind.SuperKeyword) { if (isOptionalChain(node)) { funcType = checkNonNullType( getOptionalExpressionType(checkExpression(node.expression), node.expression), node.expression ); } else { funcType = checkNonNullExpression(node.expression); } } const signatures = getSignaturesOfType(funcType && getApparentType(funcType) || unknownType, SignatureKind.Call); const candidate = signatures.length === 1 && !signatures[0].typeParameters ? signatures[0] : some(signatures, hasTypePredicateOrNeverReturnType) ? getResolvedSignature(node) : undefined; signature = links.effectsSignature = candidate && hasTypePredicateOrNeverReturnType(candidate) ? candidate : unknownSignature; } return signature === unknownSignature ? undefined : signature; } function hasTypePredicateOrNeverReturnType(signature: Signature) { return !!(getTypePredicateOfSignature(signature) || signature.declaration && (getReturnTypeFromAnnotation(signature.declaration) || unknownType).flags & TypeFlags.Never); } function getTypePredicateArgument(predicate: TypePredicate, callExpression: CallExpression) { if (predicate.kind === TypePredicateKind.Identifier || predicate.kind === TypePredicateKind.AssertsIdentifier) { return callExpression.arguments[predicate.parameterIndex]; } const invokedExpression = skipParentheses(callExpression.expression); return isAccessExpression(invokedExpression) ? skipParentheses(invokedExpression.expression) : undefined; } function reportFlowControlError(node: Node) { const block = findAncestor(node, isFunctionOrModuleBlock); 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); return node.kind === SyntaxKind.FalseKeyword || node.kind === SyntaxKind.BinaryExpression && ( (node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && (isFalseExpression((node).left) || isFalseExpression((node).right)) || (node).operatorToken.kind === SyntaxKind.BarBarToken && isFalseExpression((node).left) && isFalseExpression((node).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).antecedent; } else if (flags & FlowFlags.Call) { const signature = getEffectsSignature((flow).node); if (signature) { const predicate = getTypePredicateOfSignature(signature); if (predicate && predicate.kind === TypePredicateKind.AssertsIdentifier && !predicate.type) { const predicateArgument = (flow).node.arguments[predicate.parameterIndex]; if (predicateArgument && isFalseExpression(predicateArgument)) { return false; } } if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) { return false; } } flow = (flow).antecedent; } else if (flags & FlowFlags.BranchLabel) { // A branching point is reachable if any branch is reachable. return some((flow).antecedents, f => isReachableFlowNodeWorker(f, /*noCacheCheck*/ false)); } else if (flags & FlowFlags.LoopLabel) { // A loop is reachable if the control flow path that leads to the top is reachable. flow = (flow).antecedents![0]; } else if (flags & FlowFlags.SwitchClause) { // The control flow path representing an unmatched value in a switch statement with // no default clause is unreachable if the switch statement is exhaustive. if ((flow).clauseStart === (flow).clauseEnd && isExhaustiveSwitchStatement((flow).switchStatement)) { return false; } flow = (flow).antecedent; } else if (flags & FlowFlags.ReduceLabel) { // Cache is unreliable once we start adjusting labels lastFlowNode = undefined; const target = (flow).target; const saveAntecedents = target.antecedents; target.antecedents = (flow).antecedents; const result = isReachableFlowNodeWorker((flow).antecedent, /*noCacheCheck*/ false); target.antecedents = saveAntecedents; return result; } else { return !(flags & FlowFlags.Unreachable); } } } // Return true if the given flow node is preceded by a 'super(...)' call in every possible code path // leading to the node. function isPostSuperFlowNode(flow: FlowNode, noCacheCheck: boolean): boolean { while (true) { const flags = flow.flags; if (flags & FlowFlags.Shared) { if (!noCacheCheck) { const id = getFlowNodeId(flow); const postSuper = flowNodePostSuper[id]; return postSuper !== undefined ? postSuper : (flowNodePostSuper[id] = isPostSuperFlowNode(flow, /*noCacheCheck*/ true)); } noCacheCheck = false; } if (flags & (FlowFlags.Assignment | FlowFlags.Condition | FlowFlags.ArrayMutation | FlowFlags.SwitchClause)) { flow = (flow).antecedent; } else if (flags & FlowFlags.Call) { if ((flow).node.expression.kind === SyntaxKind.SuperKeyword) { return true; } flow = (flow).antecedent; } else if (flags & FlowFlags.BranchLabel) { // A branching point is post-super if every branch is post-super. return every((flow).antecedents, f => isPostSuperFlowNode(f, /*noCacheCheck*/ false)); } else if (flags & FlowFlags.LoopLabel) { // A loop is post-super if the control flow path that leads to the top is post-super. flow = (flow).antecedents![0]; } else if (flags & FlowFlags.ReduceLabel) { const target = (flow).target; const saveAntecedents = target.antecedents; target.antecedents = (flow).antecedents; const result = isPostSuperFlowNode((flow).antecedent, /*noCacheCheck*/ false); target.antecedents = saveAntecedents; return result; } else { // Unreachable nodes are considered post-super to silence errors return !!(flags & FlowFlags.Unreachable); } } } function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) { let key: string | undefined; let isKeySet = false; let flowDepth = 0; if (flowAnalysisDisabled) { return errorType; } if (!reference.flowNode || !couldBeUninitialized && !(declaredType.flags & TypeFlags.Narrowable)) { return declaredType; } flowInvocationCount++; const sharedFlowStart = sharedFlowCount; const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode)); sharedFlowCount = sharedFlowStart; // When the reference is 'x' in an 'x.length', 'x.push(value)', 'x.unshift(value)' or x[n] = value' operation, // we give type 'any[]' to 'x' instead of using the type determined by control flow analysis such that operations // on empty arrays are possible without implicit any errors and new element types can be inferred without // type mismatch errors. const resultType = getObjectFlags(evolvedType) & ObjectFlags.EvolvingArray && isEvolvingArrayOperationTarget(reference) ? autoArrayType : finalizeEvolvingArrayType(evolvedType); if (resultType === unreachableNeverType || reference.parent && reference.parent.kind === SyntaxKind.NonNullExpression && 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.Check, "getTypeAtFlowNode_DepthLimit", { flowId: flow.id }); flowAnalysisDisabled = true; reportFlowControlError(reference); return errorType; } flowDepth++; let sharedFlow: FlowNode | undefined; while (true) { const flags = flow.flags; if (flags & FlowFlags.Shared) { // We cache results of flow type resolution for shared nodes that were previously visited in // the same getFlowTypeOfReference invocation. A node is considered shared when it is the // antecedent of more than one node. for (let i = sharedFlowStart; i < sharedFlowCount; i++) { if (sharedFlowNodes[i] === flow) { flowDepth--; return sharedFlowTypes[i]; } } sharedFlow = flow; } let type: FlowType | undefined; if (flags & FlowFlags.Assignment) { type = getTypeAtFlowAssignment(flow); if (!type) { flow = (flow).antecedent; continue; } } else if (flags & FlowFlags.Call) { type = getTypeAtFlowCall(flow); if (!type) { flow = (flow).antecedent; continue; } } else if (flags & FlowFlags.Condition) { type = getTypeAtFlowCondition(flow); } else if (flags & FlowFlags.SwitchClause) { type = getTypeAtSwitchClause(flow); } else if (flags & FlowFlags.Label) { if ((flow).antecedents!.length === 1) { flow = (flow).antecedents![0]; continue; } type = flags & FlowFlags.BranchLabel ? getTypeAtFlowBranchLabel(flow) : getTypeAtFlowLoopLabel(flow); } else if (flags & FlowFlags.ArrayMutation) { type = getTypeAtFlowArrayMutation(flow); if (!type) { flow = (flow).antecedent; continue; } } else if (flags & FlowFlags.ReduceLabel) { const target = (flow).target; const saveAntecedents = target.antecedents; target.antecedents = (flow).antecedents; type = getTypeAtFlowNode((flow).antecedent); target.antecedents = saveAntecedents; } else if (flags & FlowFlags.Start) { // Check if we should continue with the control flow of the containing function. const container = (flow).node; if (container && container !== flowContainer && reference.kind !== SyntaxKind.PropertyAccessExpression && reference.kind !== SyntaxKind.ElementAccessExpression && reference.kind !== SyntaxKind.ThisKeyword) { flow = container.flowNode!; continue; } // At the top of the flow we have the initial type. type = initialType; } else { // Unreachable code errors are reported in the binding phase. Here we // simply return the non-auto declared type to reduce follow-on errors. type = convertAutoToAny(declaredType); } if (sharedFlow) { // Record visited node and the associated type in the cache. sharedFlowNodes[sharedFlowCount] = sharedFlow; sharedFlowTypes[sharedFlowCount] = type; sharedFlowCount++; } flowDepth--; return type; } } function getInitialOrAssignedType(flow: FlowAssignment) { const node = flow.node; return getConstraintForLocation(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ? getInitialType(node) : getAssignedType(node), reference); } function getTypeAtFlowAssignment(flow: FlowAssignment) { const node = flow.node; // Assignments only narrow the computed type if the declared type is a union type. Thus, we // only need to evaluate the assigned type if the declared type is a union type. if (isMatchingReference(reference, node)) { if (!isReachableFlowNode(flow)) { return unreachableNeverType; } if (getAssignmentTargetKind(node) === AssignmentKind.Compound) { const flowType = getTypeAtFlowNode(flow.antecedent); return createFlowType(getBaseTypeOfLiteralType(getTypeFromFlowType(flowType)), isIncomplete(flowType)); } if (declaredType === autoType || declaredType === autoArrayType) { if (isEmptyArrayAssignment(node)) { return getEvolvingArrayType(neverType); } const assignedType = getWidenedLiteralType(getInitialOrAssignedType(flow)); return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType; } if (declaredType.flags & TypeFlags.Union) { return getAssignmentReducedType(declaredType, getInitialOrAssignedType(flow)); } return declaredType; } // We didn't have a direct match. However, if the reference is a dotted name, this // may be an assignment to a left hand part of the reference. For example, for a // reference 'x.y.z', we may be at an assignment to 'x.y' or 'x'. In that case, // return the declared type. if (containsMatchingReference(reference, node)) { if (!isReachableFlowNode(flow)) { return unreachableNeverType; } // A matching dotted name might also be an expando property on a function *expression*, // in which case we continue control flow analysis back to the function's declaration if (isVariableDeclaration(node) && (isInJSFile(node) || isVarConst(node))) { const init = getDeclaredExpandoInitializer(node); if (init && (init.kind === SyntaxKind.FunctionExpression || init.kind === SyntaxKind.ArrowFunction)) { return getTypeAtFlowNode(flow.antecedent); } } return declaredType; } // for (const _ in ref) acts as a nonnull on ref if (isVariableDeclaration(node) && node.parent.parent.kind === SyntaxKind.ForInStatement && isMatchingReference(reference, node.parent.parent.expression)) { return getNonNullableTypeIfNeeded(getTypeFromFlowType(getTypeAtFlowNode(flow.antecedent))); } // Assignment doesn't affect reference return undefined; } function narrowTypeByAssertion(type: Type, expr: Expression): Type { const node = skipParentheses(expr); if (node.kind === SyntaxKind.FalseKeyword) { return unreachableNeverType; } if (node.kind === SyntaxKind.BinaryExpression) { if ((node).operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { return narrowTypeByAssertion(narrowTypeByAssertion(type, (node).left), (node).right); } if ((node).operatorToken.kind === SyntaxKind.BarBarToken) { return getUnionType([narrowTypeByAssertion(type, (node).left), narrowTypeByAssertion(type, (node).right)]); } } return narrowType(type, node, /*assumeTrue*/ true); } function getTypeAtFlowCall(flow: FlowCall): FlowType | undefined { const signature = getEffectsSignature(flow.node); if (signature) { const predicate = getTypePredicateOfSignature(signature); if (predicate && (predicate.kind === TypePredicateKind.AssertsThis || predicate.kind === TypePredicateKind.AssertsIdentifier)) { const flowType = getTypeAtFlowNode(flow.antecedent); const type = finalizeEvolvingArrayType(getTypeFromFlowType(flowType)); const narrowedType = predicate.type ? narrowTypeByTypePredicate(type, predicate, flow.node, /*assumeTrue*/ true) : predicate.kind === TypePredicateKind.AssertsIdentifier && predicate.parameterIndex >= 0 && predicate.parameterIndex < flow.node.arguments.length ? narrowTypeByAssertion(type, flow.node.arguments[predicate.parameterIndex]) : type; return narrowedType === type ? flowType : createFlowType(narrowedType, isIncomplete(flowType)); } if (getReturnTypeOfSignature(signature).flags & TypeFlags.Never) { return unreachableNeverType; } } return undefined; } function getTypeAtFlowArrayMutation(flow: FlowArrayMutation): FlowType | undefined { if (declaredType === autoType || declaredType === autoArrayType) { const node = flow.node; const expr = node.kind === SyntaxKind.CallExpression ? (node.expression).expression : (node.left).expression; if (isMatchingReference(reference, getReferenceCandidate(expr))) { const flowType = getTypeAtFlowNode(flow.antecedent); const type = getTypeFromFlowType(flowType); if (getObjectFlags(type) & ObjectFlags.EvolvingArray) { let evolvedType = type; if (node.kind === SyntaxKind.CallExpression) { for (const arg of node.arguments) { evolvedType = addEvolvingArrayElementType(evolvedType, arg); } } else { // We must get the context free expression type so as to not recur in an uncached fashion on the LHS (which causes exponential blowup in compile time) const indexType = getContextFreeTypeOfExpression((node.left).argumentExpression); if (isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { evolvedType = addEvolvingArrayElementType(evolvedType, node.right); } } return evolvedType === type ? flowType : createFlowType(evolvedType, isIncomplete(flowType)); } return flowType; } } return undefined; } function getTypeAtFlowCondition(flow: FlowCondition): FlowType { const flowType = getTypeAtFlowNode(flow.antecedent); const type = getTypeFromFlowType(flowType); if (type.flags & TypeFlags.Never) { return flowType; } // If we have an antecedent type (meaning we're reachable in some way), we first // attempt to narrow the antecedent type. If that produces the never type, and if // the antecedent type is incomplete (i.e. a transient type in a loop), then we // take the type guard as an indication that control *could* reach here once we // have the complete type. We proceed by switching to the silent never type which // doesn't report errors when operators are applied to it. Note that this is the // *only* place a silent never type is ever generated. const assumeTrue = (flow.flags & FlowFlags.TrueCondition) !== 0; const nonEvolvingType = finalizeEvolvingArrayType(type); const narrowedType = narrowType(nonEvolvingType, flow.node, assumeTrue); if (narrowedType === nonEvolvingType) { return flowType; } return createFlowType(narrowedType, isIncomplete(flowType)); } function getTypeAtSwitchClause(flow: FlowSwitchClause): FlowType { const expr = flow.switchStatement.expression; const flowType = getTypeAtFlowNode(flow.antecedent); let type = getTypeFromFlowType(flowType); if (isMatchingReference(reference, expr)) { type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); } else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) { type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); } else { if (strictNullChecks) { if (optionalChainContainsReference(expr, reference)) { type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); } else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) { type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t).value === "undefined")); } } if (isMatchingReferenceDiscriminant(expr, type)) { type = narrowTypeByDiscriminant(type, expr as AccessExpression, t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd)); } } return createFlowType(type, isIncomplete(flowType)); } function getTypeAtFlowBranchLabel(flow: FlowLabel): FlowType { const antecedentTypes: Type[] = []; let subtypeReduction = false; let seenIncomplete = false; let bypassFlow: FlowSwitchClause | undefined; for (const antecedent of flow.antecedents!) { if (!bypassFlow && antecedent.flags & FlowFlags.SwitchClause && (antecedent).clauseStart === (antecedent).clauseEnd) { // The antecedent is the bypass branch of a potentially exhaustive switch statement. bypassFlow = antecedent; continue; } const flowType = getTypeAtFlowNode(antecedent); const type = getTypeFromFlowType(flowType); // If the type at a particular antecedent path is the declared type and the // reference is known to always be assigned (i.e. when declared and initial types // are the same), there is no reason to process more antecedents since the only // possible outcome is subtypes that will be removed in the final union type anyway. if (type === declaredType && declaredType === initialType) { return type; } pushIfUnique(antecedentTypes, type); // If an antecedent type is not a subset of the declared type, we need to perform // subtype reduction. This happens when a "foreign" type is injected into the control // flow using the instanceof operator or a user defined type predicate. if (!isTypeSubsetOf(type, declaredType)) { subtypeReduction = true; } if (isIncomplete(flowType)) { seenIncomplete = true; } } if (bypassFlow) { const flowType = getTypeAtFlowNode(bypassFlow); const type = getTypeFromFlowType(flowType); // If the bypass flow contributes a type we haven't seen yet and the switch statement // isn't exhaustive, process the bypass flow type. Since exhaustiveness checks increase // the risk of circularities, we only want to perform them when they make a difference. if (!contains(antecedentTypes, type) && !isExhaustiveSwitchStatement(bypassFlow.switchStatement)) { if (type === declaredType && declaredType === initialType) { return type; } antecedentTypes.push(type); if (!isTypeSubsetOf(type, declaredType)) { subtypeReduction = true; } if (isIncomplete(flowType)) { seenIncomplete = true; } } } return createFlowType(getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal), seenIncomplete); } function getTypeAtFlowLoopLabel(flow: FlowLabel): FlowType { // If we have previously computed the control flow type for the reference at // this flow loop junction, return the cached type. const id = getFlowNodeId(flow); const cache = flowLoopCaches[id] || (flowLoopCaches[id] = new Map()); const key = getOrSetCacheKey(); if (!key) { // No cache key is generated when binding patterns are in unnarrowable situations return declaredType; } const cached = cache.get(key); if (cached) { return cached; } // If this flow loop junction and reference are already being processed, return // the union of the types computed for each branch so far, marked as incomplete. // It is possible to see an empty array in cases where loops are nested and the // back edge of the outer loop reaches an inner loop that is already being analyzed. // In such cases we restart the analysis of the inner loop, which will then see // a non-empty in-process array for the outer loop and eventually terminate because // the first antecedent of a loop junction is always the non-looping control flow // path that leads to the top. for (let i = flowLoopStart; i < flowLoopCount; i++) { if (flowLoopNodes[i] === flow && flowLoopKeys[i] === key && flowLoopTypes[i].length) { return createFlowType(getUnionOrEvolvingArrayType(flowLoopTypes[i], UnionReduction.Literal), /*incomplete*/ true); } } // Add the flow loop junction and reference to the in-process stack and analyze // each antecedent code path. const antecedentTypes: Type[] = []; let subtypeReduction = false; let firstAntecedentType: FlowType | undefined; for (const antecedent of flow.antecedents!) { let flowType; if (!firstAntecedentType) { // The first antecedent of a loop junction is always the non-looping control // flow path that leads to the top. flowType = firstAntecedentType = getTypeAtFlowNode(antecedent); } else { // All but the first antecedent are the looping control flow paths that lead // back to the loop junction. We track these on the flow loop stack. flowLoopNodes[flowLoopCount] = flow; flowLoopKeys[flowLoopCount] = key; flowLoopTypes[flowLoopCount] = antecedentTypes; flowLoopCount++; const saveFlowTypeCache = flowTypeCache; flowTypeCache = undefined; flowType = getTypeAtFlowNode(antecedent); flowTypeCache = saveFlowTypeCache; flowLoopCount--; // If we see a value appear in the cache it is a sign that control flow analysis // was restarted and completed by checkExpressionCached. We can simply pick up // the resulting type and bail out. const cached = cache.get(key); if (cached) { return cached; } } const type = getTypeFromFlowType(flowType); pushIfUnique(antecedentTypes, type); // If an antecedent type is not a subset of the declared type, we need to perform // subtype reduction. This happens when a "foreign" type is injected into the control // flow using the instanceof operator or a user defined type predicate. if (!isTypeSubsetOf(type, declaredType)) { subtypeReduction = true; } // If the type at a particular antecedent path is the declared type there is no // reason to process more antecedents since the only possible outcome is subtypes // that will be removed in the final union type anyway. if (type === declaredType) { break; } } // The result is incomplete if the first antecedent (the non-looping control flow path) // is incomplete. const result = getUnionOrEvolvingArrayType(antecedentTypes, subtypeReduction ? UnionReduction.Subtype : UnionReduction.Literal); if (isIncomplete(firstAntecedentType!)) { return createFlowType(result, /*incomplete*/ true); } cache.set(key, result); return result; } function isMatchingReferenceDiscriminant(expr: Expression, computedType: Type) { const type = declaredType.flags & TypeFlags.Union ? declaredType : computedType; if (!(type.flags & TypeFlags.Union) || !isAccessExpression(expr)) { return false; } const name = getAccessedPropertyName(expr); if (name === undefined) { return false; } return isMatchingReference(reference, expr.expression) && isDiscriminantProperty(type, name); } function narrowTypeByDiscriminant(type: Type, access: AccessExpression, narrowType: (t: Type) => Type): Type { const propName = getAccessedPropertyName(access); if (propName === undefined) { return type; } const propType = getTypeOfPropertyOfType(type, propName); if (!propType) { return type; } const narrowedPropType = narrowType(propType); return filterType(type, t => { const discriminantType = getTypeOfPropertyOrIndexSignature(t, propName); return !(discriminantType.flags & TypeFlags.Never) && isTypeComparableTo(discriminantType, narrowedPropType); }); } function narrowTypeByTruthiness(type: Type, expr: Expression, assumeTrue: boolean): Type { if (isMatchingReference(reference, expr)) { return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy); } if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) { type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } if (isMatchingReferenceDiscriminant(expr, type)) { return narrowTypeByDiscriminant(type, expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy)); } return type; } function isTypePresencePossible(type: Type, propName: __String, assumeTrue: boolean) { if (getIndexInfoOfType(type, IndexKind.String)) { return true; } const prop = getPropertyOfType(type, propName); if (prop) { return prop.flags & SymbolFlags.Optional ? true : assumeTrue; } return !assumeTrue; } function narrowByInKeyword(type: Type, literal: LiteralExpression, assumeTrue: boolean) { if (type.flags & (TypeFlags.Union | TypeFlags.Object) || isThisTypeParameter(type) || type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, t => t.symbol !== globalThisSymbol)) { const propName = escapeLeadingUnderscores(literal.text); return filterType(type, t => isTypePresencePossible(t, propName, assumeTrue)); } return type; } 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, operator, right, assumeTrue); } if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) { return narrowTypeByTypeof(type, right, 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); } } if (isMatchingReferenceDiscriminant(left, type)) { return narrowTypeByDiscriminant(type, left, t => narrowTypeByEquality(t, operator, right, assumeTrue)); } if (isMatchingReferenceDiscriminant(right, type)) { return narrowTypeByDiscriminant(type, right, t => narrowTypeByEquality(t, operator, left, assumeTrue)); } if (isMatchingConstructorReference(left)) { return narrowTypeByConstructor(type, operator, right, assumeTrue); } if (isMatchingConstructorReference(right)) { return narrowTypeByConstructor(type, operator, left, assumeTrue); } break; case SyntaxKind.InstanceOfKeyword: return narrowTypeByInstanceof(type, expr, assumeTrue); case SyntaxKind.InKeyword: const target = getReferenceCandidate(expr.right); if (isStringLiteralLike(expr.left) && isMatchingReference(reference, target)) { return narrowByInKeyword(type, expr.left, assumeTrue); } break; case SyntaxKind.CommaToken: return narrowType(type, expr.right, assumeTrue); } return type; } function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { // We are in a branch of obj?.foo === value (or any one of the other equality operators). We narrow obj as follows: // When operator is === and type of value excludes undefined, null and undefined is removed from type of obj in true branch. // When operator is !== and type of value excludes undefined, null and undefined is removed from type of obj in false branch. // When operator is == and type of value excludes null and undefined, null and undefined is removed from type of obj in true branch. // When operator is != and type of value excludes null and undefined, null and undefined is removed from type of obj in false branch. // When operator is === and type of value is undefined, null and undefined is removed from type of obj in false branch. // When operator is !== and type of value is undefined, null and undefined is removed from type of obj in true branch. // When operator is == and type of value is null or undefined, null and undefined is removed from type of obj in false branch. // When operator is != and type of value is null or undefined, null and undefined is removed from type of obj in true branch. const equalsOperator = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken; const nullableFlags = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken ? TypeFlags.Nullable : TypeFlags.Undefined; const valueType = getTypeOfExpression(value); // Note that we include any and unknown in the exclusion test because their domain includes null and undefined. const removeNullable = equalsOperator !== assumeTrue && everyType(valueType, t => !!(t.flags & nullableFlags)) || equalsOperator === assumeTrue && everyType(valueType, t => !(t.flags & (TypeFlags.AnyOrUnknown | nullableFlags))); return removeNullable ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; } function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { if (type.flags & TypeFlags.Any) { return type; } if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { assumeTrue = !assumeTrue; } const valueType = getTypeOfExpression(value); if ((type.flags & TypeFlags.Unknown) && assumeTrue && (operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken)) { if (valueType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) { return valueType; } if (valueType.flags & TypeFlags.Object) { return nonPrimitiveType; } return type; } if (valueType.flags & TypeFlags.Nullable) { if (!strictNullChecks) { return type; } const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken; const facts = doubleEquals ? assumeTrue ? TypeFacts.EQUndefinedOrNull : TypeFacts.NEUndefinedOrNull : valueType.flags & TypeFlags.Null ? assumeTrue ? TypeFacts.EQNull : TypeFacts.NENull : assumeTrue ? TypeFacts.EQUndefined : TypeFacts.NEUndefined; return getTypeWithFacts(type, facts); } if (assumeTrue) { const filterFn: (t: Type) => boolean = operator === SyntaxKind.EqualsEqualsToken ? (t => areTypesComparable(t, valueType) || isCoercibleUnderDoubleEquals(t, valueType)) : t => areTypesComparable(t, valueType); return replacePrimitivesWithLiterals(filterType(type, filterFn), valueType); } if (isUnitType(valueType)) { const regularType = getRegularTypeOfLiteralType(valueType); return filterType(type, t => isUnitType(t) ? !areTypesComparable(t, valueType) : getRegularTypeOfLiteralType(t) !== regularType); } return type; } function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type { // We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { assumeTrue = !assumeTrue; } const target = getReferenceCandidate(typeOfExpr.expression); if (!isMatchingReference(reference, target)) { if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) { return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } return type; } if (type.flags & TypeFlags.Any && literal.text === "function") { return type; } if (assumeTrue && type.flags & TypeFlags.Unknown && literal.text === "object") { // The pattern x && typeof x === 'object', where x is of type unknown, narrows x to type object. We don't // need to check for the reverse typeof x === 'object' && x since that already narrows correctly. if (typeOfExpr.parent.parent.kind === SyntaxKind.BinaryExpression) { const expr = typeOfExpr.parent.parent; if (expr.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken && expr.right === typeOfExpr.parent && containsTruthyCheck(reference, expr.left)) { return nonPrimitiveType; } } return getUnionType([nonPrimitiveType, nullType]); } const facts = assumeTrue ? typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject : typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject; const impliedType = getImpliedTypeFromTypeofGuard(type, literal.text); return getTypeWithFacts(assumeTrue && impliedType ? mapType(type, narrowUnionMemberByTypeof(impliedType)) : type, facts); } function narrowTypeBySwitchOptionalChainContainment(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number, clauseCheck: (type: Type) => boolean) { const everyClauseChecks = clauseStart !== clauseEnd && every(getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd), clauseCheck); return everyClauseChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; } function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { // We only narrow if all case expressions specify // values with unit types, except for the case where // `type` is unknown. In this instance we map object // types to the nonPrimitive type and narrow with that. const switchTypes = getSwitchClauseTypes(switchStatement); if (!switchTypes.length) { return type; } const clauseTypes = switchTypes.slice(clauseStart, clauseEnd); const hasDefaultClause = clauseStart === clauseEnd || contains(clauseTypes, neverType); if ((type.flags & TypeFlags.Unknown) && !hasDefaultClause) { let groundClauseTypes: Type[] | undefined; for (let i = 0; i < clauseTypes.length; i += 1) { const t = clauseTypes[i]; if (t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) { if (groundClauseTypes !== undefined) { groundClauseTypes.push(t); } } else if (t.flags & TypeFlags.Object) { if (groundClauseTypes === undefined) { groundClauseTypes = clauseTypes.slice(0, i); } groundClauseTypes.push(nonPrimitiveType); } else { return type; } } return getUnionType(groundClauseTypes === undefined ? clauseTypes : groundClauseTypes); } const discriminantType = getUnionType(clauseTypes); const caseType = discriminantType.flags & TypeFlags.Never ? neverType : replacePrimitivesWithLiterals(filterType(type, t => areTypesComparable(discriminantType, t)), discriminantType); if (!hasDefaultClause) { return caseType; } const defaultType = filterType(type, t => !(isUnitType(t) && contains(switchTypes, getRegularTypeOfLiteralType(t)))); return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]); } function getImpliedTypeFromTypeofGuard(type: Type, text: string) { switch (text) { case "function": return type.flags & TypeFlags.Any ? type : globalFunctionType; case "object": return type.flags & TypeFlags.Unknown ? getUnionType([nonPrimitiveType, nullType]) : type; default: return typeofTypesByName.get(text); } } // When narrowing a union type by a `typeof` guard using type-facts alone, constituent types that are // super-types of the implied guard will be retained in the final type: this is because type-facts only // filter. Instead, we would like to replace those union constituents with the more precise type implied by // the guard. For example: narrowing `{} | undefined` by `"boolean"` should produce the type `boolean`, not // the filtered type `{}`. For this reason we narrow constituents of the union individually, in addition to // filtering by type-facts. function narrowUnionMemberByTypeof(candidate: Type) { return (type: Type) => { if (isTypeSubtypeOf(type, candidate)) { return type; } if (isTypeSubtypeOf(candidate, type)) { return candidate; } if (type.flags & TypeFlags.Instantiable) { const constraint = getBaseConstraintOfType(type) || anyType; if (isTypeSubtypeOf(candidate, constraint)) { return getIntersectionType([type, candidate]); } } return type; }; } function narrowBySwitchOnTypeOf(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number): Type { const switchWitnesses = getSwitchClauseTypeOfWitnesses(switchStatement, /*retainDefault*/ true); if (!switchWitnesses.length) { return type; } // Equal start and end denotes implicit fallthrough; undefined marks explicit default clause const defaultCaseLocation = findIndex(switchWitnesses, elem => elem === undefined); const hasDefaultClause = clauseStart === clauseEnd || (defaultCaseLocation >= clauseStart && defaultCaseLocation < clauseEnd); let clauseWitnesses: string[]; let switchFacts: TypeFacts; if (defaultCaseLocation > -1) { // We no longer need the undefined denoting an explicit default case. Remove the undefined and // fix-up clauseStart and clauseEnd. This means that we don't have to worry about undefined in the // witness array. const witnesses = switchWitnesses.filter(witness => witness !== undefined); // The adjusted clause start and end after removing the `default` statement. const fixedClauseStart = defaultCaseLocation < clauseStart ? clauseStart - 1 : clauseStart; const fixedClauseEnd = defaultCaseLocation < clauseEnd ? clauseEnd - 1 : clauseEnd; clauseWitnesses = witnesses.slice(fixedClauseStart, fixedClauseEnd); switchFacts = getFactsFromTypeofSwitch(fixedClauseStart, fixedClauseEnd, witnesses, hasDefaultClause); } else { clauseWitnesses = switchWitnesses.slice(clauseStart, clauseEnd); switchFacts = getFactsFromTypeofSwitch(clauseStart, clauseEnd, switchWitnesses, hasDefaultClause); } if (hasDefaultClause) { return filterType(type, t => (getTypeFacts(t) & switchFacts) === switchFacts); } /* The implied type is the raw type suggested by a value being caught in this clause. When the clause contains a default case we ignore the implied type and try to narrow using any facts we can learn: see `switchFacts`. Example: switch (typeof x) { case 'number': case 'string': break; default: break; case 'number': case 'boolean': break } In the first clause (case `number` and `string`) the implied type is number | string. In the default clause we de not compute an implied type. In the third clause (case `number` and `boolean`) the naive implied type is number | boolean, however we use the type facts to narrow the implied type to boolean. We know that number cannot be selected because it is caught in the first clause. */ const impliedType = getTypeWithFacts(getUnionType(clauseWitnesses.map(text => getImpliedTypeFromTypeofGuard(type, text) || type)), switchFacts); return getTypeWithFacts(mapType(type, narrowUnionMemberByTypeof(impliedType)), switchFacts); } function isMatchingConstructorReference(expr: Expression) { return (isPropertyAccessExpression(expr) && idText(expr.name) === "constructor" || isElementAccessExpression(expr) && isStringLiteralLike(expr.argumentExpression) && expr.argumentExpression.text === "constructor") && isMatchingReference(reference, expr.expression); } function narrowTypeByConstructor(type: Type, operator: SyntaxKind, identifier: Expression, assumeTrue: boolean): Type { // Do not narrow when checking inequality. if (assumeTrue ? (operator !== SyntaxKind.EqualsEqualsToken && operator !== SyntaxKind.EqualsEqualsEqualsToken) : (operator !== SyntaxKind.ExclamationEqualsToken && operator !== SyntaxKind.ExclamationEqualsEqualsToken)) { return type; } // Get the type of the constructor identifier expression, if it is not a function then do not narrow. const identifierType = getTypeOfExpression(identifier); if (!isFunctionType(identifierType) && !isConstructorType(identifierType)) { return type; } // Get the prototype property of the type identifier so we can find out its type. const prototypeProperty = getPropertyOfType(identifierType, "prototype" as __String); if (!prototypeProperty) { return type; } // Get the type of the prototype, if it is undefined, or the global `Object` or `Function` types then do not narrow. const prototypeType = getTypeOfSymbol(prototypeProperty); const candidate = !isTypeAny(prototypeType) ? prototypeType : undefined; if (!candidate || candidate === globalObjectType || candidate === globalFunctionType) { return type; } // If the type that is being narrowed is `any` then just return the `candidate` type since every type is a subtype of `any`. if (isTypeAny(type)) { return candidate; } // Filter out types that are not considered to be "constructed by" the `candidate` type. return filterType(type, t => isConstructedBy(t, candidate)); function isConstructedBy(source: Type, target: Type) { // If either the source or target type are a class type then we need to check that they are the same exact type. // This is because you may have a class `A` that defines some set of properties, and another class `B` // that defines the same set of properties as class `A`, in that case they are structurally the same // type, but when you do something like `instanceOfA.constructor === B` it will return false. if (source.flags & TypeFlags.Object && getObjectFlags(source) & ObjectFlags.Class || target.flags & TypeFlags.Object && getObjectFlags(target) & ObjectFlags.Class) { return source.symbol === target.symbol; } // For all other types just check that the `source` type is a subtype of the `target` type. return isTypeSubtypeOf(source, target); } } function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { const left = getReferenceCandidate(expr.left); if (!isMatchingReference(reference, left)) { if (assumeTrue && strictNullChecks && optionalChainContainsReference(left, reference)) { return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } return type; } // Check that right operand is a function type with a prototype property const rightType = getTypeOfExpression(expr.right); if (!isTypeDerivedFrom(rightType, globalFunctionType)) { return type; } let targetType: Type | undefined; const prototypeProperty = getPropertyOfType(rightType, "prototype" as __String); if (prototypeProperty) { // Target type is type of the prototype property const prototypePropertyType = getTypeOfSymbol(prototypeProperty); if (!isTypeAny(prototypePropertyType)) { targetType = prototypePropertyType; } } // Don't narrow from 'any' if the target type is exactly 'Object' or 'Function' if (isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) { return type; } if (!targetType) { const constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct); targetType = constructSignatures.length ? getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))) : emptyObjectType; } // We can't narrow a union based off instanceof without negated types see #31576 for more info if (!assumeTrue && rightType.flags & TypeFlags.Union) { const nonConstructorTypeInUnion = find((rightType).types, (t) => !isConstructorType(t)); if (!nonConstructorTypeInUnion) return type; } return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom); } function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, isRelated: (source: Type, target: Type) => boolean) { if (!assumeTrue) { return filterType(type, t => !isRelated(t, candidate)); } // If the current type is a union type, remove all constituents that couldn't be instances of // the candidate type. If one or more constituents remain, return a union of those. if (type.flags & TypeFlags.Union) { const assignableType = filterType(type, t => isRelated(t, candidate)); if (!(assignableType.flags & TypeFlags.Never)) { return assignableType; } } // If the candidate type is a subtype of the target type, narrow to the candidate type, // if the target type is a subtype of the candidate type, narrow to the target type, // otherwise, narrow to an intersection of the two types. return isTypeSubtypeOf(candidate, type) ? candidate : isTypeSubtypeOf(type, candidate) ? type : 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); } } return type; } function narrowTypeByTypePredicate(type: Type, predicate: TypePredicate, callExpression: CallExpression, assumeTrue: boolean): Type { // Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function' if (predicate.type && !(isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType))) { const predicateArgument = getTypePredicateArgument(predicate, callExpression); if (predicateArgument) { if (isMatchingReference(reference, predicateArgument)) { return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf); } if (strictNullChecks && assumeTrue && optionalChainContainsReference(predicateArgument, reference) && !(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) { type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } if (isMatchingReferenceDiscriminant(predicateArgument, type)) { return narrowTypeByDiscriminant(type, predicateArgument as AccessExpression, t => getNarrowedType(t, predicate.type!, assumeTrue, isTypeSubtypeOf)); } } } return type; } // Narrow the given type based on the given expression having the assumed boolean value. The returned type // will be a subtype or the same type as the argument. function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type { // for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a` if (isExpressionOfOptionalChainRoot(expr) || isBinaryExpression(expr.parent) && expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken && expr.parent.left === expr) { return narrowTypeByOptionality(type, expr, assumeTrue); } switch (expr.kind) { case SyntaxKind.Identifier: case SyntaxKind.ThisKeyword: case SyntaxKind.SuperKeyword: case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: return narrowTypeByTruthiness(type, expr, assumeTrue); case SyntaxKind.CallExpression: return narrowTypeByCallExpression(type, expr, assumeTrue); case SyntaxKind.ParenthesizedExpression: case SyntaxKind.NonNullExpression: return narrowType(type, (expr).expression, assumeTrue); case SyntaxKind.BinaryExpression: return narrowTypeByBinaryExpression(type, expr, assumeTrue); case SyntaxKind.PrefixUnaryExpression: if ((expr).operator === SyntaxKind.ExclamationToken) { return narrowType(type, (expr).operand, !assumeTrue); } break; } return type; } function narrowTypeByOptionality(type: Type, expr: Expression, assumePresent: boolean): Type { if (isMatchingReference(reference, expr)) { return getTypeWithFacts(type, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull); } if (isMatchingReferenceDiscriminant(expr, type)) { return narrowTypeByDiscriminant(type, expr, t => getTypeWithFacts(t, assumePresent ? TypeFacts.NEUndefinedOrNull : TypeFacts.EQUndefinedOrNull)); } return type; } } function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { symbol = symbol.exportSymbol || symbol; // If we have an identifier or a property access at the given location, if the location is // an dotted name expression, and if the location is not an assignment target, obtain the type // of the expression (which will reflect control flow analysis). If the expression indeed // resolved to the given symbol, return the narrowed type. if (location.kind === SyntaxKind.Identifier) { if (isRightSideOfQualifiedNameOrPropertyAccess(location)) { location = location.parent; } if (isExpressionNode(location) && !isAssignmentTarget(location)) { const type = getTypeOfExpression(location); if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) { return type; } } } // 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 getTypeOfSymbol(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 is assigned anywhere within its declaring function. function isParameterAssigned(symbol: Symbol) { const func = getRootDeclaration(symbol.valueDeclaration).parent; const links = getNodeLinks(func); if (!(links.flags & NodeCheckFlags.AssignmentsMarked)) { links.flags |= NodeCheckFlags.AssignmentsMarked; if (!hasParentWithAssignmentsMarked(func)) { markParameterAssignments(func); } } return symbol.isAssigned || false; } function hasParentWithAssignmentsMarked(node: Node) { return !!findAncestor(node.parent, node => isFunctionLike(node) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked)); } function markParameterAssignments(node: Node) { if (node.kind === SyntaxKind.Identifier) { if (isAssignmentTarget(node)) { const symbol = getResolvedSymbol(node); if (symbol.valueDeclaration && getRootDeclaration(symbol.valueDeclaration).kind === SyntaxKind.Parameter) { symbol.isAssigned = true; } } } else { forEachChild(node, markParameterAssignments); } } function isConstVariable(symbol: Symbol) { return symbol.flags & SymbolFlags.Variable && (getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const) !== 0 && getTypeOfSymbol(symbol) !== autoArrayType; } /** remove undefined from the annotated type of a parameter when there is an initializer (that doesn't include undefined) */ function removeOptionalityFromDeclaredType(declaredType: Type, declaration: VariableLikeDeclaration): Type { if (pushTypeResolution(declaration.symbol, TypeSystemPropertyName.DeclaredType)) { const annotationIncludesUndefined = strictNullChecks && declaration.kind === SyntaxKind.Parameter && declaration.initializer && getFalsyFlags(declaredType) & TypeFlags.Undefined && !(getFalsyFlags(checkExpression(declaration.initializer)) & TypeFlags.Undefined); popTypeResolution(); return annotationIncludesUndefined ? getTypeWithFacts(declaredType, TypeFacts.NEUndefined) : declaredType; } else { reportCircularityError(declaration.symbol); return declaredType; } } function isConstraintPosition(node: Node) { const parent = node.parent; return parent.kind === SyntaxKind.PropertyAccessExpression || parent.kind === SyntaxKind.CallExpression && (parent).expression === node || parent.kind === SyntaxKind.ElementAccessExpression && (parent).expression === node || parent.kind === SyntaxKind.BindingElement && (parent).name === node && !!(parent).initializer; } function typeHasNullableConstraint(type: Type) { return type.flags & TypeFlags.InstantiableNonPrimitive && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.Nullable); } function getConstraintForLocation(type: Type, node: Node): Type; function getConstraintForLocation(type: Type | undefined, node: Node): Type | undefined; function getConstraintForLocation(type: Type, node: Node): Type | undefined { // When a node is the left hand expression of a property access, element access, or call expression, // and the type of the node includes type variables with constraints that are nullable, we fetch the // apparent type of the node *before* performing control flow analysis such that narrowings apply to // the constraint type. if (type && isConstraintPosition(node) && forEachType(type, typeHasNullableConstraint)) { return mapType(getWidenedType(type), getBaseConstraintOrType); } return type; } function isExportOrExportExpression(location: Node) { return !!findAncestor(location, e => e.parent && isExportAssignment(e.parent) && e.parent.expression === e && isEntityNameExpression(e)); } function markAliasReferenced(symbol: Symbol, location: Node) { if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !isInTypeQuery(location) && !getTypeOnlyAliasDeclaration(symbol)) { if (compilerOptions.preserveConstEnums && isExportOrExportExpression(location) || !isConstEnumOrConstEnumOnlyModule(resolveAlias(symbol))) { markAliasSymbolAsReferenced(symbol); } else { markConstEnumAliasAsReferenced(symbol); } } } function checkIdentifier(node: Identifier): Type { const symbol = getResolvedSymbol(node); if (symbol === unknownSymbol) { return errorType; } // As noted in ECMAScript 6 language spec, arrow functions never have an arguments objects. // Although in down-level emit of arrow function, we emit it using function expression which means that // arguments objects will be bound to the inner object; emitting arrow function natively in ES6, arguments objects // will be bound to non-arrow function that contain this arrow function. This results in inconsistent behavior. // To avoid that we will give an error to users if they use arguments objects in arrow function so that they // can explicitly bound arguments objects if (symbol === argumentsSymbol) { const container = getContainingFunction(node)!; if (languageVersion < ScriptTarget.ES2015) { if (container.kind === SyntaxKind.ArrowFunction) { error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_arrow_function_in_ES3_and_ES5_Consider_using_a_standard_function_expression); } else if (hasSyntacticModifier(container, ModifierFlags.Async)) { error(node, Diagnostics.The_arguments_object_cannot_be_referenced_in_an_async_function_or_method_in_ES3_and_ES5_Consider_using_a_standard_function_or_method); } } getNodeLinks(container).flags |= NodeCheckFlags.CaptureArguments; return getTypeOfSymbol(symbol); } // We should only mark aliases as referenced if there isn't a local value declaration // for the symbol. Also, don't mark any property access expression LHS - checkPropertyAccessExpression will handle that if (!(node.parent && isPropertyAccessExpression(node.parent) && node.parent.expression === node)) { markAliasReferenced(symbol, node); } const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol); const sourceSymbol = localOrExportSymbol.flags & SymbolFlags.Alias ? resolveAlias(localOrExportSymbol) : localOrExportSymbol; if (getDeclarationNodeFlagsFromSymbol(sourceSymbol) & NodeFlags.Deprecated && isUncalledFunctionReference(node.parent, sourceSymbol)) { errorOrSuggestion(/* isError */ false, node, Diagnostics._0_is_deprecated, node.escapedText as string); } let declaration: Declaration | undefined = localOrExportSymbol.valueDeclaration; if (localOrExportSymbol.flags & SymbolFlags.Class) { // Due to the emit for class decorators, any reference to the class from inside of the class body // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind // behavior of class names in ES6. if (declaration.kind === SyntaxKind.ClassDeclaration && nodeIsDecorated(declaration as ClassDeclaration)) { let container = getContainingClass(node); while (container !== undefined) { if (container === declaration && container.name !== node) { getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference; getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass; break; } container = getContainingClass(container); } } else if (declaration.kind === SyntaxKind.ClassExpression) { // When we emit a class expression with static members that contain a reference // to the constructor in the initializer, we will need to substitute that // binding with an alias as the class name is not in scope. let container = getThisContainer(node, /*includeArrowFunctions*/ false); while (container.kind !== SyntaxKind.SourceFile) { if (container.parent === declaration) { if (container.kind === SyntaxKind.PropertyDeclaration && hasSyntacticModifier(container, ModifierFlags.Static)) { getNodeLinks(declaration).flags |= NodeCheckFlags.ClassWithConstructorReference; getNodeLinks(node).flags |= NodeCheckFlags.ConstructorReferenceInClass; } break; } container = getThisContainer(container, /*includeArrowFunctions*/ false); } } } checkNestedBlockScopedBinding(node, symbol); const type = getConstraintForLocation(getTypeOfSymbol(localOrExportSymbol), node); const assignmentKind = getAssignmentTargetKind(node); if (assignmentKind) { if (!(localOrExportSymbol.flags & SymbolFlags.Variable) && !(isInJSFile(node) && localOrExportSymbol.flags & SymbolFlags.ValueModule)) { error(node, Diagnostics.Cannot_assign_to_0_because_it_is_not_a_variable, symbolToString(symbol)); return errorType; } if (isReadonlySymbol(localOrExportSymbol)) { if (localOrExportSymbol.flags & SymbolFlags.Variable) { error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_constant, symbolToString(symbol)); } else { error(node, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, symbolToString(symbol)); } return errorType; } } const isAlias = localOrExportSymbol.flags & SymbolFlags.Alias; // We only narrow variables and parameters occurring in a non-assignment position. For all other // entities we simply return the declared type. if (localOrExportSymbol.flags & SymbolFlags.Variable) { if (assignmentKind === AssignmentKind.Definite) { return type; } } else if (isAlias) { declaration = find(symbol.declarations, isSomeImportDeclaration); } else { return type; } if (!declaration) { return type; } // The declaration container is the innermost function that encloses the declaration of the variable // or parameter. The flow container is the innermost function starting with which we analyze the control // flow graph to determine the control flow based type. const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter; const declarationContainer = getControlFlowContainer(declaration); let flowContainer = getControlFlowContainer(node); const isOuterVariable = flowContainer !== declarationContainer; const isSpreadDestructuringAssignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent); const isModuleExports = symbol.flags & SymbolFlags.ModuleExports; // When the control flow originates in a function expression or arrow function and we are referencing // a const variable or parameter from an outer function, we extend the origin of the control flow // analysis to include the immediately enclosing function. while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression || flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethod(flowContainer)) && (isConstVariable(localOrExportSymbol) || isParameter && !isParameterAssigned(localOrExportSymbol))) { flowContainer = getControlFlowContainer(flowContainer); } // We only look for uninitialized variables in strict null checking mode, and only when we can analyze // the entire control flow graph from the variable's declaration (i.e. when the flow container and // declaration container are the same). const assumeInitialized = isParameter || isAlias || isOuterVariable || isSpreadDestructuringAssignmentTarget || isModuleExports || isBindingElement(declaration) || type !== autoType && type !== autoArrayType && (!strictNullChecks || (type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void)) !== 0 || isInTypeQuery(node) || node.parent.kind === SyntaxKind.ExportSpecifier) || node.parent.kind === SyntaxKind.NonNullExpression || declaration.kind === SyntaxKind.VariableDeclaration && (declaration).exclamationToken || declaration.flags & NodeFlags.Ambient; const initialType = assumeInitialized ? (isParameter ? removeOptionalityFromDeclaredType(type, declaration as VariableLikeDeclaration) : type) : type === autoType || type === autoArrayType ? undefinedType : getOptionalType(type); const flowType = getFlowTypeOfReference(node, type, initialType, flowContainer, !assumeInitialized); // A variable is considered uninitialized when it is possible to analyze the entire control flow graph // from declaration to use, and when the variable's declared type doesn't include undefined but the // control flow based type does include undefined. if (!isEvolvingArrayOperationTarget(node) && (type === autoType || type === autoArrayType)) { if (flowType === autoType || flowType === autoArrayType) { if (noImplicitAny) { error(getNameOfDeclaration(declaration), Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined, symbolToString(symbol), typeToString(flowType)); error(node, Diagnostics.Variable_0_implicitly_has_an_1_type, symbolToString(symbol), typeToString(flowType)); } return convertAutoToAny(flowType); } } else if (!assumeInitialized && !(getFalsyFlags(type) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); // Return the declared type to reduce follow-on errors return type; } return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; } function isInsideFunction(node: Node, threshold: Node): boolean { return !!findAncestor(node, n => n === threshold ? "quit" : isFunctionLike(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 checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void { if (languageVersion >= ScriptTarget.ES2015 || (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.Class)) === 0 || 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 usedInFunction = isInsideFunction(node.parent, container); let current = container; let containedInIterationStatement = false; while (current && !nodeStartsNewLexicalEnvironment(current)) { if (isIterationStatement(current, /*lookInLabeledStatements*/ false)) { containedInIterationStatement = true; break; } current = current.parent; } if (containedInIterationStatement) { if (usedInFunction) { // 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(current).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 (usedInFunction) { getNodeLinks(symbol.valueDeclaration).flags |= NodeCheckFlags.CapturedBlockScopedBinding; } } function isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement) { const links = getNodeLinks(node); return !!links && contains(links.capturedBlockScopeBindings, getSymbolOfNode(decl)); } function isAssignedInBodyOfForStatement(node: Identifier, container: ForStatement): boolean { // skip parenthesized nodes let current: Node = node; while (current.parent.kind === SyntaxKind.ParenthesizedExpression) { current = current.parent; } // check if node is used as LHS in some assignment expression let isAssigned = false; if (isAssignmentTarget(current)) { isAssigned = true; } else if ((current.parent.kind === SyntaxKind.PrefixUnaryExpression || current.parent.kind === SyntaxKind.PostfixUnaryExpression)) { const expr = current.parent; isAssigned = expr.operator === SyntaxKind.PlusPlusToken || expr.operator === SyntaxKind.MinusMinusToken; } if (!isAssigned) { return false; } // at this point we know that node is the target of assignment // now check that modification happens inside the statement part of the ForStatement return !!findAncestor(current, n => n === container ? "quit" : n === container.statement); } function captureLexicalThis(node: Node, container: Node): void { getNodeLinks(node).flags |= NodeCheckFlags.LexicalThis; if (container.kind === SyntaxKind.PropertyDeclaration || container.kind === SyntaxKind.Constructor) { const classNode = container.parent; getNodeLinks(classNode).flags |= NodeCheckFlags.CaptureThis; } else { getNodeLinks(container).flags |= NodeCheckFlags.CaptureThis; } } function findFirstSuperCall(node: Node): SuperCall | undefined { return isSuperCall(node) ? node : isFunctionLike(node) ? undefined : forEachChild(node, findFirstSuperCall); } /** * Check if the given class-declaration extends null then return true. * Otherwise, return false * @param classDecl a class declaration to check if it extends null */ function classDeclarationExtendsNull(classDecl: ClassDeclaration): boolean { const classSymbol = getSymbolOfNode(classDecl); const classInstanceType = getDeclaredTypeOfSymbol(classSymbol); const baseConstructorType = getBaseConstructorTypeOfClass(classInstanceType); return baseConstructorType === nullWideningType; } function checkThisBeforeSuper(node: Node, container: Node, diagnosticMessage: DiagnosticMessage) { const containingClassDecl = container.parent; const baseTypeNode = getClassExtendsHeritageElement(containingClassDecl); // If a containing class does not have extends clause or the class extends null // skip checking whether super statement is called before "this" accessing. if (baseTypeNode && !classDeclarationExtendsNull(containingClassDecl)) { if (node.flowNode && !isPostSuperFlowNode(node.flowNode, /*noCacheCheck*/ false)) { error(node, diagnosticMessage); } } } function checkThisExpression(node: Node): Type { // Stop at the first arrow function so that we can // tell whether 'this' needs to be captured. let container = getThisContainer(node, /* includeArrowFunctions */ true); let capturedByArrowFunction = false; if (container.kind === SyntaxKind.Constructor) { checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class); } // Now skip arrow functions to get the "real" owner of 'this'. if (container.kind === SyntaxKind.ArrowFunction) { container = getThisContainer(container, /* includeArrowFunctions */ false); capturedByArrowFunction = true; } switch (container.kind) { case SyntaxKind.ModuleDeclaration: error(node, Diagnostics.this_cannot_be_referenced_in_a_module_or_namespace_body); // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks break; case SyntaxKind.EnumDeclaration: error(node, Diagnostics.this_cannot_be_referenced_in_current_location); // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks break; case SyntaxKind.Constructor: if (isInConstructorArgumentInitializer(node, container)) { error(node, Diagnostics.this_cannot_be_referenced_in_constructor_arguments); // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks } break; case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: if (hasSyntacticModifier(container, ModifierFlags.Static) && !(compilerOptions.target === ScriptTarget.ESNext && compilerOptions.useDefineForClassFields)) { error(node, Diagnostics.this_cannot_be_referenced_in_a_static_property_initializer); // do not return here so in case if lexical this is captured - it will be reflected in flags on NodeLinks } break; case SyntaxKind.ComputedPropertyName: error(node, Diagnostics.this_cannot_be_referenced_in_a_computed_property_name); break; } // When targeting es6, mark that we'll need to capture `this` in its lexically bound scope. if (capturedByArrowFunction && languageVersion < ScriptTarget.ES2015) { captureLexicalThis(node, container); } const type = tryGetThisTypeAt(node, /*includeGlobalThis*/ true, container); if (noImplicitThis) { const globalThisType = getTypeOfSymbol(globalThisSymbol); if (type === globalThisType && capturedByArrowFunction) { error(node, Diagnostics.The_containing_arrow_function_captures_the_global_value_of_this); } else if (!type) { // With noImplicitThis, functions may not reference 'this' if it has type 'any' const diag = error(node, Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation); if (!isSourceFile(container)) { const outsideThis = tryGetThisTypeAt(container); if (outsideThis && outsideThis !== globalThisType) { addRelatedInfo(diag, createDiagnosticForNode(container, Diagnostics.An_outer_value_of_this_is_shadowed_by_this_container)); } } } } return type || anyType; } function tryGetThisTypeAt(node: Node, includeGlobalThis = true, container = getThisContainer(node, /*includeArrowFunctions*/ false)): Type | undefined { const isInJS = isInJSFile(node); if (isFunctionLike(container) && (!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) { let thisType = getThisTypeOfDeclaration(container) || isInJS && getTypeForThisExpressionFromJSDoc(container); // Note: a parameter initializer should refer to class-this unless function-this is explicitly annotated. // If this is a function in a JS file, it might be a class method. if (!thisType) { const className = getClassNameFromPrototypeMethod(container); if (isInJS && className) { const classSymbol = checkExpression(className).symbol; if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) { thisType = (getDeclaredTypeOfSymbol(classSymbol) as InterfaceType).thisType; } } else if (isJSConstructor(container)) { thisType = (getDeclaredTypeOfSymbol(getMergedSymbol(container.symbol)) as InterfaceType).thisType; } thisType ||= getContextualThisParameterType(container); } if (thisType) { return getFlowTypeOfReference(node, thisType); } } if (isClassLike(container.parent)) { const symbol = getSymbolOfNode(container.parent); const type = hasSyntacticModifier(container, ModifierFlags.Static) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!; return getFlowTypeOfReference(node, type); } if (isSourceFile(container)) { // look up in the source file's locals or exports if (container.commonJsModuleIndicator) { const fileSymbol = getSymbolOfNode(container); return fileSymbol && getTypeOfSymbol(fileSymbol); } else if (container.externalModuleIndicator) { // TODO: Maybe issue a better error than 'object is possibly undefined' return undefinedType; } else if (includeGlobalThis) { return getTypeOfSymbol(globalThisSymbol); } } } function getExplicitThisType(node: Expression) { const container = getThisContainer(node, /*includeArrowFunctions*/ false); if (isFunctionLike(container)) { const signature = getSignatureFromDeclaration(container); if (signature.thisParameter) { return getExplicitTypeOfSymbol(signature.thisParameter); } } if (isClassLike(container.parent)) { const symbol = getSymbolOfNode(container.parent); return hasSyntacticModifier(container, ModifierFlags.Static) ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol) as InterfaceType).thisType!; } } function getClassNameFromPrototypeMethod(container: Node) { // Check if it's the RHS of a x.prototype.y = function [name]() { .... } if (container.kind === SyntaxKind.FunctionExpression && isBinaryExpression(container.parent) && getAssignmentDeclarationKind(container.parent) === AssignmentDeclarationKind.PrototypeProperty) { // Get the 'x' of 'x.prototype.y = container' return ((container.parent // x.prototype.y = container .left as PropertyAccessExpression) // x.prototype.y .expression as PropertyAccessExpression) // x.prototype .expression; // x } // x.prototype = { method() { } } else if (container.kind === SyntaxKind.MethodDeclaration && container.parent.kind === SyntaxKind.ObjectLiteralExpression && isBinaryExpression(container.parent.parent) && getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.Prototype) { return (container.parent.parent.left as PropertyAccessExpression).expression; } // x.prototype = { method: function() { } } else if (container.kind === SyntaxKind.FunctionExpression && container.parent.kind === SyntaxKind.PropertyAssignment && container.parent.parent.kind === SyntaxKind.ObjectLiteralExpression && isBinaryExpression(container.parent.parent.parent) && getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.Prototype) { return (container.parent.parent.parent.left as PropertyAccessExpression).expression; } // Object.defineProperty(x, "method", { value: function() { } }); // Object.defineProperty(x, "method", { set: (x: () => void) => void }); // Object.defineProperty(x, "method", { get: () => function() { }) }); else if (container.kind === SyntaxKind.FunctionExpression && isPropertyAssignment(container.parent) && isIdentifier(container.parent.name) && (container.parent.name.escapedText === "value" || container.parent.name.escapedText === "get" || container.parent.name.escapedText === "set") && isObjectLiteralExpression(container.parent.parent) && isCallExpression(container.parent.parent.parent) && container.parent.parent.parent.arguments[2] === container.parent.parent && getAssignmentDeclarationKind(container.parent.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) { return (container.parent.parent.parent.arguments[0] as PropertyAccessExpression).expression; } // Object.defineProperty(x, "method", { value() { } }); // Object.defineProperty(x, "method", { set(x: () => void) {} }); // Object.defineProperty(x, "method", { get() { return () => {} } }); else if (isMethodDeclaration(container) && isIdentifier(container.name) && (container.name.escapedText === "value" || container.name.escapedText === "get" || container.name.escapedText === "set") && isObjectLiteralExpression(container.parent) && isCallExpression(container.parent.parent) && container.parent.parent.arguments[2] === container.parent && getAssignmentDeclarationKind(container.parent.parent) === AssignmentDeclarationKind.ObjectDefinePrototypeProperty) { return (container.parent.parent.arguments[0] as PropertyAccessExpression).expression; } } function getTypeForThisExpressionFromJSDoc(node: Node) { const jsdocType = getJSDocType(node); if (jsdocType && jsdocType.kind === SyntaxKind.JSDocFunctionType) { const jsDocFunctionType = jsdocType; if (jsDocFunctionType.parameters.length > 0 && jsDocFunctionType.parameters[0].name && (jsDocFunctionType.parameters[0].name as Identifier).escapedText === InternalSymbolName.This) { return getTypeFromTypeNode(jsDocFunctionType.parameters[0].type!); } } const thisTag = getJSDocThisTag(node); if (thisTag && thisTag.typeExpression) { return getTypeFromTypeNode(thisTag.typeExpression); } } function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean { return !!findAncestor(node, n => isFunctionLikeDeclaration(n) ? "quit" : n.kind === SyntaxKind.Parameter && n.parent === constructorDecl); } function checkSuperExpression(node: Node): Type { const isCallExpression = node.parent.kind === SyntaxKind.CallExpression && (node.parent).expression === node; const immediateContainer = getSuperContainer(node, /*stopOnFunctions*/ true); let container = immediateContainer; let needToCaptureLexicalThis = false; // adjust the container reference in case if super is used inside arrow functions with arbitrarily deep nesting if (!isCallExpression) { while (container && container.kind === SyntaxKind.ArrowFunction) { container = getSuperContainer(container, /*stopOnFunctions*/ true); needToCaptureLexicalThis = languageVersion < ScriptTarget.ES2015; } } const canUseSuperExpression = isLegalUsageOfSuperExpression(container); let nodeCheckFlag: NodeCheckFlags = 0; if (!canUseSuperExpression) { // issue more specific error if super is used in computed property name // class A { foo() { return "1" }} // class B { // [super.foo()]() {} // } const current = findAncestor(node, n => n === container ? "quit" : n.kind === SyntaxKind.ComputedPropertyName); if (current && current.kind === SyntaxKind.ComputedPropertyName) { error(node, Diagnostics.super_cannot_be_referenced_in_a_computed_property_name); } else if (isCallExpression) { error(node, Diagnostics.Super_calls_are_not_permitted_outside_constructors_or_in_nested_functions_inside_constructors); } else if (!container || !container.parent || !(isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression)) { error(node, Diagnostics.super_can_only_be_referenced_in_members_of_derived_classes_or_object_literal_expressions); } else { error(node, Diagnostics.super_property_access_is_permitted_only_in_a_constructor_member_function_or_member_accessor_of_a_derived_class); } return errorType; } if (!isCallExpression && immediateContainer.kind === SyntaxKind.Constructor) { checkThisBeforeSuper(node, container, Diagnostics.super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class); } if (hasSyntacticModifier(container, ModifierFlags.Static) || isCallExpression) { nodeCheckFlag = NodeCheckFlags.SuperStatic; } else { nodeCheckFlag = NodeCheckFlags.SuperInstance; } getNodeLinks(node).flags |= nodeCheckFlag; // Due to how we emit async functions, we need to specialize the emit for an async method that contains a `super` reference. // This is due to the fact that we emit the body of an async function inside of a generator function. As generator // functions cannot reference `super`, we emit a helper inside of the method body, but outside of the generator. This helper // uses an arrow function, which is permitted to reference `super`. // // There are two primary ways we can access `super` from within an async method. The first is getting the value of a property // or indexed access on super, either as part of a right-hand-side expression or call expression. The second is when setting the value // of a property or indexed access, either as part of an assignment expression or destructuring assignment. // // The simplest case is reading a value, in which case we will emit something like the following: // // // ts // ... // async asyncMethod() { // let x = await super.asyncMethod(); // return x; // } // ... // // // js // ... // asyncMethod() { // const _super = Object.create(null, { // asyncMethod: { get: () => super.asyncMethod }, // }); // return __awaiter(this, arguments, Promise, function *() { // let x = yield _super.asyncMethod.call(this); // return x; // }); // } // ... // // The more complex case is when we wish to assign a value, especially as part of a destructuring assignment. As both cases // are legal in ES6, but also likely less frequent, we only emit setters if there is an assignment: // // // ts // ... // async asyncMethod(ar: Promise) { // [super.a, super.b] = await ar; // } // ... // // // js // ... // asyncMethod(ar) { // const _super = Object.create(null, { // a: { get: () => super.a, set: (v) => super.a = v }, // b: { get: () => super.b, set: (v) => super.b = v } // }; // return __awaiter(this, arguments, Promise, function *() { // [_super.a, _super.b] = yield ar; // }); // } // ... // // Creating an object that has getter and setters instead of just an accessor function is required for destructuring assignments // as a call expression cannot be used as the target of a destructuring assignment while a property access can. // // For element access expressions (`super[x]`), we emit a generic helper that forwards the element access in both situations. if (container.kind === SyntaxKind.MethodDeclaration && hasSyntacticModifier(container, ModifierFlags.Async)) { if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) { getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuperBinding; } else { getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuper; } } if (needToCaptureLexicalThis) { // call expressions are allowed only in constructors so they should always capture correct 'this' // super property access expressions can also appear in arrow functions - // in this case they should also use correct lexical this captureLexicalThis(node.parent, container); } if (container.parent.kind === SyntaxKind.ObjectLiteralExpression) { if (languageVersion < ScriptTarget.ES2015) { error(node, Diagnostics.super_is_only_allowed_in_members_of_object_literal_expressions_when_option_target_is_ES2015_or_higher); return errorType; } else { // for object literal assume that type of 'super' is 'any' return anyType; } } // at this point the only legal case for parent is ClassLikeDeclaration const classLikeDeclaration = container.parent; if (!getClassExtendsHeritageElement(classLikeDeclaration)) { error(node, Diagnostics.super_can_only_be_referenced_in_a_derived_class); return errorType; } const classType = getDeclaredTypeOfSymbol(getSymbolOfNode(classLikeDeclaration)); const baseClassType = classType && getBaseTypes(classType)[0]; if (!baseClassType) { return errorType; } if (container.kind === SyntaxKind.Constructor && isInConstructorArgumentInitializer(node, container)) { // issue custom error message for super property access in constructor arguments (to be aligned with old compiler) error(node, Diagnostics.super_cannot_be_referenced_in_constructor_arguments); return errorType; } return nodeCheckFlag === NodeCheckFlags.SuperStatic ? getBaseConstructorTypeOfClass(classType) : getTypeWithThisArgument(baseClassType, classType.thisType); function isLegalUsageOfSuperExpression(container: Node): boolean { if (!container) { return false; } if (isCallExpression) { // TS 1.0 SPEC (April 2014): 4.8.1 // Super calls are only permitted in constructors of derived classes return container.kind === SyntaxKind.Constructor; } else { // TS 1.0 SPEC (April 2014) // 'super' property access is allowed // - In a constructor, instance member function, instance member accessor, or instance member variable initializer where this references a derived class instance // - In a static member function or static member accessor // topmost container must be something that is directly nested in the class declaration\object literal expression if (isClassLike(container.parent) || container.parent.kind === SyntaxKind.ObjectLiteralExpression) { if (hasSyntacticModifier(container, ModifierFlags.Static)) { return container.kind === SyntaxKind.MethodDeclaration || container.kind === SyntaxKind.MethodSignature || container.kind === SyntaxKind.GetAccessor || container.kind === SyntaxKind.SetAccessor; } else { return container.kind === SyntaxKind.MethodDeclaration || container.kind === SyntaxKind.MethodSignature || container.kind === SyntaxKind.GetAccessor || container.kind === SyntaxKind.SetAccessor || container.kind === SyntaxKind.PropertyDeclaration || container.kind === SyntaxKind.PropertySignature || container.kind === SyntaxKind.Constructor; } } } return false; } } function getContainingObjectLiteral(func: SignatureDeclaration): ObjectLiteralExpression | undefined { return (func.kind === SyntaxKind.MethodDeclaration || func.kind === SyntaxKind.GetAccessor || func.kind === SyntaxKind.SetAccessor) && func.parent.kind === SyntaxKind.ObjectLiteralExpression ? func.parent : func.kind === SyntaxKind.FunctionExpression && func.parent.kind === SyntaxKind.PropertyAssignment ? func.parent.parent : undefined; } function getThisTypeArgument(type: Type): Type | undefined { return getObjectFlags(type) & ObjectFlags.Reference && (type).target === globalThisType ? getTypeArguments(type)[0] : undefined; } function getThisTypeFromContextualType(type: Type): Type | undefined { return mapType(type, t => { return t.flags & TypeFlags.Intersection ? forEach((t).types, getThisTypeArgument) : getThisTypeArgument(t); }); } function getContextualThisParameterType(func: SignatureDeclaration): Type | undefined { if (func.kind === SyntaxKind.ArrowFunction) { return undefined; } if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) { const contextualSignature = getContextualSignature(func); if (contextualSignature) { const thisParameter = contextualSignature.thisParameter; if (thisParameter) { return getTypeOfSymbol(thisParameter); } } } const inJs = isInJSFile(func); if (noImplicitThis || inJs) { const containingLiteral = getContainingObjectLiteral(func); if (containingLiteral) { // We have an object literal method. Check if the containing object literal has a contextual type // that includes a ThisType. If so, T is the contextual type for 'this'. We continue looking in // any directly enclosing object literals. const contextualType = getApparentTypeOfContextualType(containingLiteral); let literal = containingLiteral; let type = contextualType; while (type) { const thisType = getThisTypeFromContextualType(type); if (thisType) { return instantiateType(thisType, getMapperFromContext(getInferenceContext(containingLiteral))); } if (literal.parent.kind !== SyntaxKind.PropertyAssignment) { break; } literal = literal.parent.parent; type = getApparentTypeOfContextualType(literal); } // There was no contextual ThisType for the containing object literal, so the contextual type // for 'this' is the non-null form of the contextual type for the containing object literal or // the type of the object literal itself. return getWidenedType(contextualType ? getNonNullableType(contextualType) : checkExpressionCached(containingLiteral)); } // In an assignment of the form 'obj.xxx = function(...)' or 'obj[xxx] = function(...)', the // contextual type for 'this' is 'obj'. const parent = walkUpParenthesizedExpressions(func.parent); if (parent.kind === SyntaxKind.BinaryExpression && (parent).operatorToken.kind === SyntaxKind.EqualsToken) { const target = (parent).left; if (isAccessExpression(target)) { const { expression } = target; // Don't contextually type `this` as `exports` in `exports.Point = function(x, y) { this.x = x; this.y = y; }` if (inJs && isIdentifier(expression)) { const sourceFile = getSourceFileOfNode(parent); if (sourceFile.commonJsModuleIndicator && getResolvedSymbol(expression) === sourceFile.symbol) { return undefined; } } return getWidenedType(checkExpressionCached(expression)); } } } return undefined; } // Return contextual type of parameter or undefined if no contextual type is available function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type | undefined { const func = parameter.parent; if (!isContextSensitiveFunctionOrObjectLiteralMethod(func)) { return undefined; } const iife = getImmediatelyInvokedFunctionExpression(func); if (iife && iife.arguments) { const args = getEffectiveCallArguments(iife); const indexOfParameter = func.parameters.indexOf(parameter); if (parameter.dotDotDotToken) { return getSpreadArgumentType(args, indexOfParameter, args.length, anyType, /*context*/ undefined, CheckMode.Normal); } const links = getNodeLinks(iife); const cached = links.resolvedSignature; links.resolvedSignature = anySignature; const type = indexOfParameter < args.length ? getWidenedLiteralType(checkExpression(args[indexOfParameter])) : parameter.initializer ? undefined : undefinedWideningType; links.resolvedSignature = cached; return type; } const contextualSignature = getContextualSignature(func); if (contextualSignature) { const index = func.parameters.indexOf(parameter) - (getThisParameter(func) ? 1 : 0); return parameter.dotDotDotToken && lastOrUndefined(func.parameters) === parameter ? getRestTypeAtPosition(contextualSignature, index) : tryGetTypeAtPosition(contextualSignature, index); } } function getContextualTypeForVariableLikeDeclaration(declaration: VariableLikeDeclaration): Type | undefined { const typeNode = getEffectiveTypeAnnotationNode(declaration); if (typeNode) { return getTypeFromTypeNode(typeNode); } switch (declaration.kind) { case SyntaxKind.Parameter: return getContextuallyTypedParameterType(declaration); case SyntaxKind.BindingElement: return getContextualTypeForBindingElement(declaration); case SyntaxKind.PropertyDeclaration: if (hasSyntacticModifier(declaration, ModifierFlags.Static)) { return getContextualTypeForStaticPropertyDeclaration(declaration); } // By default, do nothing and return undefined - only the above cases have context implied by a parent } } function getContextualTypeForBindingElement(declaration: BindingElement): Type | undefined { const parent = declaration.parent.parent; const name = declaration.propertyName || declaration.name; const parentType = getContextualTypeForVariableLikeDeclaration(parent) || parent.kind !== SyntaxKind.BindingElement && parent.initializer && checkDeclarationInitializer(parent); if (!parentType || isBindingPattern(name) || isComputedNonLiteralName(name)) return undefined; if (parent.name.kind === SyntaxKind.ArrayBindingPattern) { const index = indexOfNode(declaration.parent.elements, declaration); if (index < 0) return undefined; return getContextualTypeForElementExpression(parentType, index); } const nameType = getLiteralTypeFromPropertyName(name); if (isTypeUsableAsPropertyName(nameType)) { const text = getPropertyNameFromType(nameType); return getTypeOfPropertyOfType(parentType, text); } } function getContextualTypeForStaticPropertyDeclaration(declaration: PropertyDeclaration): Type | undefined { const parentType = isExpression(declaration.parent) && getContextualType(declaration.parent); if (!parentType) return undefined; return getTypeOfPropertyOfContextualType(parentType, getSymbolOfNode(declaration).escapedName); } // In a variable, parameter or property declaration with a type annotation, // the contextual type of an initializer expression is the type of the variable, parameter or property. // Otherwise, in a parameter declaration of a contextually typed function expression, // the contextual type of an initializer expression is the contextual type of the parameter. // Otherwise, in a variable or parameter declaration with a binding pattern name, // the contextual type of an initializer expression is the type implied by the binding pattern. // Otherwise, in a binding pattern inside a variable or parameter declaration, // the contextual type of an initializer expression is the type annotation of the containing declaration, if present. function getContextualTypeForInitializerExpression(node: Expression, contextFlags?: ContextFlags): Type | undefined { const declaration = node.parent; if (hasInitializer(declaration) && node === declaration.initializer) { const result = getContextualTypeForVariableLikeDeclaration(declaration); if (result) { return result; } if (!(contextFlags! & ContextFlags.SkipBindingPatterns) && isBindingPattern(declaration.name)) { // This is less a contextual type and more an implied shape - in some cases, this may be undesirable return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false); } } return undefined; } function getContextualTypeForReturnExpression(node: Expression): Type | undefined { const func = getContainingFunction(node); if (func) { let contextualReturnType = getContextualReturnType(func); if (contextualReturnType) { const functionFlags = getFunctionFlags(func); if (functionFlags & FunctionFlags.Generator) { // Generator or AsyncGenerator function const use = functionFlags & FunctionFlags.Async ? IterationUse.AsyncGeneratorReturnType : IterationUse.GeneratorReturnType; const iterationTypes = getIterationTypesOfIterable(contextualReturnType, use, /*errorNode*/ undefined); if (!iterationTypes) { return undefined; } contextualReturnType = iterationTypes.returnType; // falls through to unwrap Promise for AsyncGenerators } if (functionFlags & FunctionFlags.Async) { // Async function or AsyncGenerator function const contextualAwaitedType = mapType(contextualReturnType, getAwaitedType); return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); } return contextualReturnType; // Regular function or Generator function } } return undefined; } function getContextualTypeForAwaitOperand(node: AwaitExpression, contextFlags?: ContextFlags): Type | undefined { const contextualType = getContextualType(node, contextFlags); if (contextualType) { const contextualAwaitedType = getAwaitedType(contextualType); return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]); } return undefined; } function getContextualTypeForYieldOperand(node: YieldExpression): Type | undefined { const func = getContainingFunction(node); if (func) { const functionFlags = getFunctionFlags(func); const contextualReturnType = getContextualReturnType(func); if (contextualReturnType) { return node.asteriskToken ? contextualReturnType : getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, contextualReturnType, (functionFlags & FunctionFlags.Async) !== 0); } } return undefined; } function isInParameterInitializerBeforeContainingFunction(node: Node) { let inBindingInitializer = false; while (node.parent && !isFunctionLike(node.parent)) { if (isParameter(node.parent) && (inBindingInitializer || node.parent.initializer === node)) { return true; } if (isBindingElement(node.parent) && node.parent.initializer === node) { inBindingInitializer = true; } node = node.parent; } return false; } function getContextualIterationType(kind: IterationTypeKind, functionDecl: SignatureDeclaration): Type | undefined { const isAsync = !!(getFunctionFlags(functionDecl) & FunctionFlags.Async); const contextualReturnType = getContextualReturnType(functionDecl); if (contextualReturnType) { return getIterationTypeOfGeneratorFunctionReturnType(kind, contextualReturnType, isAsync) || undefined; } return undefined; } function getContextualReturnType(functionDecl: SignatureDeclaration): Type | undefined { // If the containing function has a return type annotation, is a constructor, or is a get accessor whose // corresponding set accessor has a type annotation, return statements in the function are contextually typed const returnType = getReturnTypeFromAnnotation(functionDecl); if (returnType) { return returnType; } // Otherwise, if the containing function is contextually typed by a function type with exactly one call signature // and that call signature is non-generic, return statements are contextually typed by the return type of the signature const signature = getContextualSignatureForFunctionLikeDeclaration(functionDecl); if (signature && !isResolvingReturnTypeOfSignature(signature)) { return getReturnTypeOfSignature(signature); } 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 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); } return getTypeAtPosition(signature, argIndex); } function getContextualTypeForSubstitutionExpression(template: TemplateExpression, substitutionExpression: Expression) { if (template.parent.kind === SyntaxKind.TaggedTemplateExpression) { return getContextualTypeForArgument(template.parent, substitutionExpression); } return undefined; } function getContextualTypeForBinaryOperand(node: Expression, contextFlags?: ContextFlags): Type | undefined { const binaryExpression = node.parent; 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; } } // 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: return getTypeOfExpression(binaryExpression.left); case AssignmentDeclarationKind.Property: case AssignmentDeclarationKind.ExportsProperty: case AssignmentDeclarationKind.Prototype: case AssignmentDeclarationKind.PrototypeProperty: if (isPossiblyAliasedThisProperty(binaryExpression, kind)) { return getContextualTypeForThisPropertyAssignment(binaryExpression, kind); } // If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration. // See `bindStaticPropertyAssignment` in `binder.ts`. else if (!binaryExpression.left.symbol) { return getTypeOfExpression(binaryExpression.left); } else { const decl = binaryExpression.left.symbol.valueDeclaration; if (!decl) { return undefined; } const lhs = cast(binaryExpression.left, isAccessExpression); const overallAnnotation = getEffectiveTypeAnnotationNode(decl); if (overallAnnotation) { return getTypeFromTypeNode(overallAnnotation); } else if (isIdentifier(lhs.expression)) { const id = lhs.expression; const parentSymbol = resolveName(id, id.escapedText, SymbolFlags.Value, undefined, id.escapedText, /*isUse*/ true); if (parentSymbol) { const annotated = parentSymbol.valueDeclaration && getEffectiveTypeAnnotationNode(parentSymbol.valueDeclaration); if (annotated) { const nameStr = getElementOrPropertyAccessName(lhs); if (nameStr !== undefined) { return getTypeOfPropertyOfContextualType(getTypeFromTypeNode(annotated), nameStr); } } return undefined; } } return isInJSFile(decl) ? undefined : getTypeOfExpression(binaryExpression.left); } case AssignmentDeclarationKind.ModuleExports: case AssignmentDeclarationKind.ThisProperty: return getContextualTypeForThisPropertyAssignment(binaryExpression, kind); case AssignmentDeclarationKind.ObjectDefinePropertyValue: case AssignmentDeclarationKind.ObjectDefinePropertyExports: case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: return Debug.fail("Does not apply"); default: return Debug.assertNever(kind); } } function isPossiblyAliasedThisProperty(declaration: BinaryExpression, kind = getAssignmentDeclarationKind(declaration)) { if (kind === AssignmentDeclarationKind.ThisProperty) { return true; } if (!isInJSFile(declaration) || kind !== AssignmentDeclarationKind.Property || !isIdentifier((declaration.left as AccessExpression).expression)) { return false; } const name = ((declaration.left as AccessExpression).expression as Identifier).escapedText; const symbol = resolveName(declaration.left, name, SymbolFlags.Value, undefined, undefined, /*isUse*/ true, /*excludeGlobals*/ true); return isThisInitializedDeclaration(symbol?.valueDeclaration); } function getContextualTypeForThisPropertyAssignment(binaryExpression: BinaryExpression, kind: AssignmentDeclarationKind): 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; } } } if (kind === AssignmentDeclarationKind.ModuleExports) return undefined; const thisAccess = cast(binaryExpression.left, isAccessExpression); if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) { return undefined; } const thisType = checkThisExpression(thisAccess.expression); const nameStr = getElementOrPropertyAccessName(thisAccess); return nameStr !== undefined && getTypeOfPropertyOfContextualType(thisType, nameStr) || undefined; } function isCircularMappedProperty(symbol: Symbol) { return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(symbol).type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0); } function getTypeOfPropertyOfContextualType(type: Type, name: __String) { return mapType(type, t => { if (isGenericMappedType(t)) { const constraint = getConstraintTypeFromMappedType(t); const constraintOfConstraint = getBaseConstraintOfType(constraint) || constraint; const propertyNameType = getLiteralType(unescapeLeadingUnderscores(name)); if (isTypeAssignableTo(propertyNameType, constraintOfConstraint)) { return substituteIndexedMappedType(t, propertyNameType); } } else if (t.flags & TypeFlags.StructuredType) { const prop = getPropertyOfType(t, name); if (prop) { return isCircularMappedProperty(prop) ? undefined : getTypeOfSymbol(prop); } if (isTupleType(t)) { const restType = getRestTypeOfTupleType(t); if (restType && isNumericLiteralName(name) && +name >= 0) { return restType; } } return isNumericLiteralName(name) && getIndexTypeOfContextualType(t, IndexKind.Number) || getIndexTypeOfContextualType(t, IndexKind.String); } return undefined; }, /*noReductions*/ true); } function getIndexTypeOfContextualType(type: Type, kind: IndexKind) { return mapType(type, t => getIndexTypeOfStructuredType(t, kind), /*noReductions*/ true); } // In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of // the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one // exists. Otherwise, it is the type of the string index signature in T, if one exists. function getContextualTypeForObjectLiteralMethod(node: MethodDeclaration, contextFlags?: ContextFlags): Type | undefined { Debug.assert(isObjectLiteralMethod(node)); if (node.flags & NodeFlags.InWithStatement) { // We cannot answer semantic questions within a with block, do not proceed any further return undefined; } return getContextualTypeForObjectLiteralElement(node, contextFlags); } function getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike, contextFlags?: ContextFlags) { const objectLiteral = element.parent; const type = getApparentTypeOfContextualType(objectLiteral, contextFlags); if (type) { if (!hasNonBindableDynamicName(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 symbolName = getSymbolOfNode(element).escapedName; const propertyType = getTypeOfPropertyOfContextualType(type, symbolName); if (propertyType) { return propertyType; } } return isNumericName(element.name!) && getIndexTypeOfContextualType(type, IndexKind.Number) || getIndexTypeOfContextualType(type, IndexKind.String); } return undefined; } // In an array literal contextually typed by a type T, the contextual type of an element expression at index N is // the type of the property with the numeric name N in T, if one exists. Otherwise, if T has a numeric index signature, // it is the type of the numeric index signature in T. Otherwise, in ES6 and higher, the contextual type is the iterated // type of T. function getContextualTypeForElementExpression(arrayContextualType: Type | undefined, index: number): Type | undefined { return arrayContextualType && ( getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String) || mapType( arrayContextualType, t => getIteratedTypeOrElementType(IterationUse.Element, t, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false), /*noReductions*/ true)); } // In a contextually typed conditional expression, the true/false expressions are contextually typed by the same type. function getContextualTypeForConditionalOperand(node: Expression, contextFlags?: ContextFlags): Type | undefined { const conditional = node.parent; return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional, contextFlags) : undefined; } function getContextualTypeForChildJsxExpression(node: JsxElement, child: JsxChild) { const attributesType = getApparentTypeOfContextualType(node.openingElement.tagName); // JSX expression is in children of JSX Element, we will look for an "children" atttribute (we get the name from JSX.ElementAttributesProperty) const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node)); if (!(attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "")) { return undefined; } const realChildren = getSemanticJsxChildren(node.children); const childIndex = realChildren.indexOf(child); const childFieldType = getTypeOfPropertyOfContextualType(attributesType, jsxChildrenPropertyName); return childFieldType && (realChildren.length === 1 ? childFieldType : mapType(childFieldType, t => { if (isArrayLikeType(t)) { return getIndexedAccessType(t, getLiteralType(childIndex)); } else { return t; } }, /*noReductions*/ true)); } function getContextualTypeForJsxExpression(node: JsxExpression): Type | undefined { const exprParent = node.parent; return isJsxAttributeLike(exprParent) ? getContextualType(node) : isJsxElement(exprParent) ? getContextualTypeForChildJsxExpression(exprParent, node) : undefined; } function getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined { // When we trying to resolve JsxOpeningLikeElement as a stateless function element, we will already give its attributes a contextual type // which is a type of the parameter of the signature we are trying out. // If there is no contextual type (e.g. we are trying to resolve stateful component), get attributes type from resolving element's tagName if (isJsxAttribute(attribute)) { const attributesType = getApparentTypeOfContextualType(attribute.parent); if (!attributesType || isTypeAny(attributesType)) { return undefined; } return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText); } else { return getContextualType(attribute.parent); } } // Return true if the given expression is possibly a discriminant value. We limit the kinds of // expressions we check to those that don't depend on their contextual type in order not to cause // recursive (and possibly infinite) invocations of getContextualType. function isPossiblyDiscriminantValue(node: Expression): boolean { switch (node.kind) { case SyntaxKind.StringLiteral: case SyntaxKind.NumericLiteral: case SyntaxKind.BigIntLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: case SyntaxKind.NullKeyword: case SyntaxKind.Identifier: case SyntaxKind.UndefinedKeyword: return true; case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ParenthesizedExpression: return isPossiblyDiscriminantValue((node).expression); case SyntaxKind.JsxExpression: return !(node as JsxExpression).expression || isPossiblyDiscriminantValue((node as JsxExpression).expression!); } return false; } function discriminateContextualTypeByObjectMembers(node: ObjectLiteralExpression, contextualType: UnionType) { return discriminateTypeByDiscriminableItems(contextualType, map( filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.PropertyAssignment && isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName)), prop => ([() => checkExpression((prop as PropertyAssignment).initializer), prop.symbol.escapedName] as [() => Type, __String]) ), isTypeAssignableTo, contextualType ); } function discriminateContextualTypeByJSXAttributes(node: JsxAttributes, contextualType: UnionType) { return discriminateTypeByDiscriminableItems(contextualType, 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) : (() => checkExpression((prop as JsxAttribute).initializer!)), prop.symbol.escapedName] as [() => Type, __String]) ), isTypeAssignableTo, contextualType ); } // Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily // be "pushed" onto a node using the contextualType property. function getApparentTypeOfContextualType(node: Expression | MethodDeclaration, contextFlags?: ContextFlags): Type | undefined { const contextualType = isObjectLiteralMethod(node) ? getContextualTypeForObjectLiteralMethod(node, contextFlags) : getContextualType(node, contextFlags); const instantiatedType = instantiateContextualType(contextualType, node, contextFlags); if (instantiatedType && !(contextFlags && contextFlags & ContextFlags.NoConstraints && instantiatedType.flags & TypeFlags.TypeVariable)) { const apparentType = mapType(instantiatedType, getApparentType, /*noReductions*/ true); if (apparentType.flags & TypeFlags.Union) { if (isObjectLiteralExpression(node)) { return discriminateContextualTypeByObjectMembers(node, apparentType as UnionType); } else if (isJsxAttributes(node)) { return discriminateContextualTypeByJSXAttributes(node, apparentType as UnionType); } } return apparentType; } } // If the given contextual type contains instantiable types and if a mapper representing // return type inferences is available, instantiate those types using that mapper. function instantiateContextualType(contextualType: Type | undefined, node: Node, contextFlags?: ContextFlags): Type | undefined { if (contextualType && maybeTypeOfKind(contextualType, TypeFlags.Instantiable)) { const inferenceContext = getInferenceContext(node); // If no inferences have been made, nothing is gained from instantiating as type parameters // would just be replaced with their defaults similar to the apparent type. if (inferenceContext && some(inferenceContext.inferences, hasInferenceCandidates)) { // For contextual signatures we incorporate all inferences made so far, e.g. from return // types as well as arguments to the left in a function call. if (contextFlags && contextFlags & ContextFlags.Signature) { return instantiateInstantiableTypes(contextualType, inferenceContext.nonFixingMapper); } // For other purposes (e.g. determining whether to produce literal types) we only // incorporate inferences made from the return type in a function call. if (inferenceContext.returnMapper) { return instantiateInstantiableTypes(contextualType, inferenceContext.returnMapper); } } } return contextualType; } // This function is similar to instantiateType, except that (a) it only instantiates types that // are classified as instantiable (i.e. it doesn't instantiate object types), and (b) it performs // no reductions on instantiated union types. function instantiateInstantiableTypes(type: Type, mapper: TypeMapper): Type { if (type.flags & TypeFlags.Instantiable) { return instantiateType(type, mapper); } if (type.flags & TypeFlags.Union) { return getUnionType(map((type).types, t => instantiateInstantiableTypes(t, mapper)), UnionReduction.None); } if (type.flags & TypeFlags.Intersection) { return getIntersectionType(map((type).types, t => instantiateInstantiableTypes(t, mapper))); } return type; } /** * Whoa! Do you really want to use this function? * * Unless you're trying to get the *non-apparent* type for a * value-literal type or you're authoring relevant portions of this algorithm, * you probably meant to use 'getApparentTypeOfContextualType'. * Otherwise this may not be very useful. * * In cases where you *are* working on this function, you should understand * when it is appropriate to use 'getContextualType' and 'getApparentTypeOfContextualType'. * * - Use 'getContextualType' when you are simply going to propagate the result to the expression. * - Use 'getApparentTypeOfContextualType' when you're going to need the members of the type. * * @param node the expression whose contextual type will be returned. * @returns the contextual type of an expression. */ function getContextualType(node: Expression, contextFlags?: ContextFlags): Type | undefined { if (node.flags & NodeFlags.InWithStatement) { // We cannot answer semantic questions within a with block, do not proceed any further return undefined; } if (node.contextualType) { return node.contextualType; } const { parent } = node; switch (parent.kind) { case SyntaxKind.VariableDeclaration: case SyntaxKind.Parameter: case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.BindingElement: return getContextualTypeForInitializerExpression(node, contextFlags); case SyntaxKind.ArrowFunction: case SyntaxKind.ReturnStatement: return getContextualTypeForReturnExpression(node); case SyntaxKind.YieldExpression: return getContextualTypeForYieldOperand(parent); case SyntaxKind.AwaitExpression: return getContextualTypeForAwaitOperand(parent, contextFlags); case SyntaxKind.CallExpression: if ((parent).expression.kind === SyntaxKind.ImportKeyword) { return stringType; } /* falls through */ case SyntaxKind.NewExpression: return getContextualTypeForArgument(parent, node); case SyntaxKind.TypeAssertionExpression: case SyntaxKind.AsExpression: return isConstTypeReference((parent).type) ? tryFindWhenConstTypeReference(parent) : getTypeFromTypeNode((parent).type); case SyntaxKind.BinaryExpression: return getContextualTypeForBinaryOperand(node, contextFlags); case SyntaxKind.PropertyAssignment: case SyntaxKind.ShorthandPropertyAssignment: return getContextualTypeForObjectLiteralElement(parent, contextFlags); case SyntaxKind.SpreadAssignment: return getApparentTypeOfContextualType(parent.parent as ObjectLiteralExpression, contextFlags); case SyntaxKind.ArrayLiteralExpression: { const arrayLiteral = parent; const type = getApparentTypeOfContextualType(arrayLiteral, contextFlags); return getContextualTypeForElementExpression(type, indexOfNode(arrayLiteral.elements, node)); } case SyntaxKind.ConditionalExpression: return getContextualTypeForConditionalOperand(node, contextFlags); case SyntaxKind.TemplateSpan: Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression); return getContextualTypeForSubstitutionExpression(parent.parent, node); case SyntaxKind.ParenthesizedExpression: { // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. const tag = isInJSFile(parent) ? getJSDocTypeTag(parent) : undefined; return tag ? getTypeFromTypeNode(tag.typeExpression.type) : getContextualType(parent, contextFlags); } case SyntaxKind.JsxExpression: return getContextualTypeForJsxExpression(parent); case SyntaxKind.JsxAttribute: case SyntaxKind.JsxSpreadAttribute: return getContextualTypeForJsxAttribute(parent); case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxSelfClosingElement: return getContextualJsxElementAttributesType(parent, contextFlags); } return undefined; function tryFindWhenConstTypeReference(node: Expression) { if(isCallLikeExpression(node.parent)){ return getContextualTypeForArgument(node.parent, node); } return undefined; } } function getInferenceContext(node: Node) { const ancestor = findAncestor(node, n => !!n.inferenceContext); return ancestor && ancestor.inferenceContext!; } function getContextualJsxElementAttributesType(node: JsxOpeningLikeElement, contextFlags?: ContextFlags) { if (isJsxOpeningElement(node) && node.parent.contextualType && contextFlags !== ContextFlags.Completions) { // Contextually applied type is moved from attributes up to the outer jsx attributes so when walking up from the children they get hit // _However_ to hit them from the _attributes_ we must look for them here; otherwise we'll used the declared type // (as below) instead! return node.parent.contextualType; } return getContextualTypeForArgumentAtIndex(node, 0); } function getEffectiveFirstArgumentForJsxSignature(signature: Signature, node: JsxOpeningLikeElement) { return getJsxReferenceKind(node) !== JsxReferenceKind.Component ? getJsxPropsTypeFromCallSignature(signature, node) : getJsxPropsTypeFromClassType(signature, node); } function getJsxPropsTypeFromCallSignature(sig: Signature, context: JsxOpeningLikeElement) { let propsType = getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType); propsType = getJsxManagedAttributesFromLocatedAttributes(context, getJsxNamespaceAt(context), propsType); const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); if (intrinsicAttribs !== errorType) { propsType = intersectTypes(intrinsicAttribs, propsType); } return propsType; } function getJsxPropsTypeForSignatureFromMember(sig: Signature, forcedLookupLocation: __String) { if (sig.unionSignatures) { // 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.unionSignatures) { const instance = getReturnTypeOfSignature(signature); if (isTypeAny(instance)) { return instance; } const propType = getTypeOfPropertyOfType(instance, forcedLookupLocation); if (!propType) { return; } results.push(propType); } return getIntersectionType(results); } const instanceType = getReturnTypeOfSignature(sig); return isTypeAny(instanceType) ? instanceType : getTypeOfPropertyOfType(instanceType, forcedLookupLocation); } function getStaticTypeOfReferencedJsxConstructor(context: JsxOpeningLikeElement) { if (isJsxIntrinsicIdentifier(context.tagName)) { const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(context); const fakeSignature = createSignatureForJSXIntrinsic(context, result); return getOrCreateTypeFromSignature(fakeSignature); } const tagType = checkExpressionCached(context.tagName); if (tagType.flags & TypeFlags.StringLiteral) { const result = getIntrinsicAttributesTypeFromStringLiteralType(tagType as StringLiteralType, context); if (!result) { return errorType; } const fakeSignature = createSignatureForJSXIntrinsic(context, result); return getOrCreateTypeFromSignature(fakeSignature); } return tagType; } function getJsxManagedAttributesFromLocatedAttributes(context: JsxOpeningLikeElement, ns: Symbol, attributesType: Type) { const managedSym = getJsxLibraryManagedAttributes(ns); if (managedSym) { const declaredManagedType = getDeclaredTypeOfSymbol(managedSym); const ctorType = getStaticTypeOfReferencedJsxConstructor(context); if (length((declaredManagedType as GenericType).typeParameters) >= 2) { const args = fillMissingTypeArguments([ctorType, attributesType], (declaredManagedType as GenericType).typeParameters, 2, isInJSFile(context)); return createTypeReference((declaredManagedType as GenericType), args); } else if (length(declaredManagedType.aliasTypeArguments) >= 2) { const args = fillMissingTypeArguments([ctorType, attributesType], declaredManagedType.aliasTypeArguments, 2, isInJSFile(context)); return getTypeAliasInstantiation(declaredManagedType.aliasSymbol!, args); } } return attributesType; } function getJsxPropsTypeFromClassType(sig: Signature, context: JsxOpeningLikeElement) { const ns = getJsxNamespaceAt(context); const forcedLookupLocation = getJsxElementPropertiesName(ns); let attributesType = forcedLookupLocation === undefined // If there is no type ElementAttributesProperty, return the type of the first parameter of the signature, which should be the props type ? getTypeOfFirstParameterOfSignatureWithFallback(sig, unknownType) : forcedLookupLocation === "" // If there is no e.g. 'props' member in ElementAttributesProperty, use the element class type instead ? getReturnTypeOfSignature(sig) // Otherwise get the type of the property on the signature return type : getJsxPropsTypeForSignatureFromMember(sig, forcedLookupLocation); if (!attributesType) { // There is no property named 'props' on this instance type if (!!forcedLookupLocation && !!length(context.attributes.properties)) { error(context, Diagnostics.JSX_element_class_does_not_support_attributes_because_it_does_not_have_a_0_property, unescapeLeadingUnderscores(forcedLookupLocation)); } return unknownType; } attributesType = getJsxManagedAttributesFromLocatedAttributes(context, ns, attributesType); if (isTypeAny(attributesType)) { // Props is of type 'any' or unknown return attributesType; } else { // Normal case -- add in IntrinsicClassElements and IntrinsicElements let apparentAttributesType = attributesType; const intrinsicClassAttribs = getJsxType(JsxNames.IntrinsicClassAttributes, context); if (intrinsicClassAttribs !== errorType) { const typeParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(intrinsicClassAttribs.symbol); const hostClassType = getReturnTypeOfSignature(sig); apparentAttributesType = intersectTypes( typeParams ? createTypeReference(intrinsicClassAttribs, fillMissingTypeArguments([hostClassType], typeParams, getMinTypeArgumentCount(typeParams), isInJSFile(context))) : intrinsicClassAttribs, apparentAttributesType ); } const intrinsicAttribs = getJsxType(JsxNames.IntrinsicAttributes, context); if (intrinsicAttribs !== errorType) { apparentAttributesType = intersectTypes(intrinsicAttribs, apparentAttributesType); } return apparentAttributesType; } } // If the given type is an object or union type with a single signature, and if that signature has at // least as many parameters as the given function, return the signature. Otherwise return undefined. function getContextualCallSignature(type: Type, node: SignatureDeclaration): Signature | undefined { const signatures = getSignaturesOfType(type, SignatureKind.Call); if (signatures.length === 1) { const signature = signatures[0]; if (!isAritySmaller(signature, node)) { return signature; } } } /** If the contextual signature has fewer parameters than the function expression, do not use it */ function isAritySmaller(signature: Signature, target: SignatureDeclaration) { let targetParameterCount = 0; for (; targetParameterCount < target.parameters.length; targetParameterCount++) { const param = target.parameters[targetParameterCount]; if (param.initializer || param.questionToken || param.dotDotDotToken || isJSDocOptionalParameter(param)) { break; } } if (target.parameters.length && parameterIsThisKeyword(target.parameters[0])) { targetParameterCount--; } return !hasEffectiveRestParameter(signature) && getParameterCount(signature) < targetParameterCount; } function isFunctionExpressionOrArrowFunction(node: Node): node is FunctionExpression | ArrowFunction { return node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction; } function getContextualSignatureForFunctionLikeDeclaration(node: FunctionLikeDeclaration): Signature | undefined { // Only function expressions, arrow functions, and object literal methods are contextually typed. return isFunctionExpressionOrArrowFunction(node) || isObjectLiteralMethod(node) ? getContextualSignature(node) : undefined; } // Return the contextual signature for a given expression node. A contextual type provides a // contextual signature if it has a single call signature and if that call signature is non-generic. // If the contextual type is a union type, get the signature from each type possible and if they are // all identical ignoring their return type, the result is same signature but with return type as // union type of return types from these signatures function getContextualSignature(node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature | undefined { Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); const typeTagSignature = getSignatureOfTypeTag(node); if (typeTagSignature) { return typeTagSignature; } const type = getApparentTypeOfContextualType(node, ContextFlags.Signature); if (!type) { return undefined; } if (!(type.flags & TypeFlags.Union)) { return getContextualCallSignature(type, node); } let signatureList: Signature[] | undefined; const types = (type).types; for (const current of types) { const signature = getContextualCallSignature(current, node); if (signature) { if (!signatureList) { // This signature will contribute to contextual union signature signatureList = [signature]; } else if (!compareSignaturesIdentical(signatureList[0], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true, compareTypesIdentical)) { // Signatures aren't identical, do not use return undefined; } else { // Use this signature for contextual union signature signatureList.push(signature); } } } // Result is union of signatures collected (return type is union of return types of this signature set) if (signatureList) { return signatureList.length === 1 ? signatureList[0] : createUnionSignature(signatureList[0], signatureList); } } function checkSpreadExpression(node: SpreadElement, checkMode?: CheckMode): Type { if (languageVersion < ScriptTarget.ES2015) { checkExternalEmitHelpers(node, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArrays); } const arrayOrIterableType = checkExpression(node.expression, checkMode); return checkIteratedTypeOrElementType(IterationUse.Spread, arrayOrIterableType, undefinedType, node.expression); } function checkSyntheticExpression(node: SyntheticExpression): Type { return node.isSpread ? getIndexedAccessType(node.type, numberType) : node.type; } function hasDefaultValue(node: BindingElement | Expression): boolean { return (node.kind === SyntaxKind.BindingElement && !!(node).initializer) || (node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.EqualsToken); } function checkArrayLiteral(node: ArrayLiteralExpression, checkMode: CheckMode | undefined, forceTuple: boolean | undefined): Type { const elements = node.elements; const elementCount = elements.length; const elementTypes: Type[] = []; const elementFlags: ElementFlags[] = []; const contextualType = getApparentTypeOfContextualType(node); const inDestructuringPattern = isAssignmentTarget(node); const inConstContext = isConstContext(node); for (let i = 0; i < elementCount; i++) { const e = elements[i]; if (e.kind === SyntaxKind.SpreadElement) { if (languageVersion < ScriptTarget.ES2015) { checkExternalEmitHelpers(e, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArrays); } const spreadType = checkExpression((e).expression, checkMode, forceTuple); if (isArrayLikeType(spreadType)) { elementTypes.push(spreadType); elementFlags.push(ElementFlags.Variadic); } else if (inDestructuringPattern) { // Given the following situation: // var c: {}; // [...c] = ["", 0]; // // c is represented in the tree as a spread element in an array literal. // But c really functions as a rest element, and its purpose is to provide // a contextual type for the right hand side of the assignment. Therefore, // instead of calling checkExpression on "...c", which will give an error // if c is not iterable/array-like, we need to act as if we are trying to // get the contextual element type from it. So we do something similar to // getContextualTypeForElementExpression, which will crucially not error // if there is no index type / iterated type. const restElementType = getIndexTypeOfType(spreadType, IndexKind.Number) || getIteratedTypeOrElementType(IterationUse.Destructuring, spreadType, undefinedType, /*errorNode*/ undefined, /*checkAssignability*/ false) || unknownType; elementTypes.push(restElementType); elementFlags.push(ElementFlags.Rest); } else { elementTypes.push(checkIteratedTypeOrElementType(IterationUse.Spread, spreadType, undefinedType, (e).expression)); elementFlags.push(ElementFlags.Rest); } } else { const elementContextualType = getContextualTypeForElementExpression(contextualType, elementTypes.length); const type = checkExpressionForMutableLocation(e, checkMode, elementContextualType, forceTuple); elementTypes.push(type); elementFlags.push(ElementFlags.Required); } } if (inDestructuringPattern) { return createTupleType(elementTypes, elementFlags); } if (forceTuple || inConstContext || contextualType && forEachType(contextualType, isTupleLikeType)) { return createArrayLiteralType(createTupleType(elementTypes, elementFlags, /*readonly*/ inConstContext)); } return createArrayLiteralType(createArrayType(elementTypes.length ? getUnionType(sameMap(elementTypes, (t, i) => elementFlags[i] & ElementFlags.Variadic ? getIndexedAccessTypeOrUndefined(t, numberType) || anyType : t), UnionReduction.Subtype) : strictNullChecks ? implicitNeverType : undefinedWideningType, inConstContext)); } function createArrayLiteralType(type: Type) { if (!(getObjectFlags(type) & ObjectFlags.Reference)) { return type; } let literalType = (type).literalType; if (!literalType) { literalType = (type).literalType = cloneTypeReference(type); literalType.objectFlags |= ObjectFlags.ArrayLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; } return literalType; } function isNumericName(name: DeclarationName): boolean { switch (name.kind) { case SyntaxKind.ComputedPropertyName: return isNumericComputedName(name); case SyntaxKind.Identifier: return isNumericLiteralName(name.escapedText); case SyntaxKind.NumericLiteral: case SyntaxKind.StringLiteral: return isNumericLiteralName(name.text); default: return false; } } function isNumericComputedName(name: ComputedPropertyName): boolean { // It seems odd to consider an expression of type Any to result in a numeric name, // but this behavior is consistent with checkIndexedAccess return isTypeAssignableToKind(checkComputedPropertyName(name), TypeFlags.NumberLike); } function isInfinityOrNaNString(name: string | __String): boolean { return name === "Infinity" || name === "-Infinity" || name === "NaN"; } function isNumericLiteralName(name: string | __String) { // The intent of numeric names is that // - they are names with text in a numeric form, and that // - setting properties/indexing with them is always equivalent to doing so with the numeric literal 'numLit', // acquired by applying the abstract 'ToNumber' operation on the name's text. // // The subtlety is in the latter portion, as we cannot reliably say that anything that looks like a numeric literal is a numeric name. // In fact, it is the case that the text of the name must be equal to 'ToString(numLit)' for this to hold. // // Consider the property name '"0xF00D"'. When one indexes with '0xF00D', they are actually indexing with the value of 'ToString(0xF00D)' // according to the ECMAScript specification, so it is actually as if the user indexed with the string '"61453"'. // Thus, the text of all numeric literals equivalent to '61543' such as '0xF00D', '0xf00D', '0170015', etc. are not valid numeric names // because their 'ToString' representation is not equal to their original text. // This is motivated by ECMA-262 sections 9.3.1, 9.8.1, 11.1.5, and 11.2.1. // // Here, we test whether 'ToString(ToNumber(name))' is exactly equal to 'name'. // The '+' prefix operator is equivalent here to applying the abstract ToNumber operation. // Applying the 'toString()' method on a number gives us the abstract ToString operation on a number. // // Note that this accepts the values 'Infinity', '-Infinity', and 'NaN', and that this is intentional. // This is desired behavior, because when indexing with them as numeric entities, you are indexing // with the strings '"Infinity"', '"-Infinity"', and '"NaN"' respectively. return (+name).toString() === name; } function checkComputedPropertyName(node: ComputedPropertyName): Type { const links = getNodeLinks(node.expression); if (!links.resolvedType) { links.resolvedType = checkExpression(node.expression); // This will allow types number, string, symbol or any. It will also allow enums, the unknown // type, and any union of these types (like string | number). if (links.resolvedType.flags & TypeFlags.Nullable || !isTypeAssignableToKind(links.resolvedType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) && !isTypeAssignableTo(links.resolvedType, stringNumberSymbolType)) { error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_symbol_or_any); } else { checkThatExpressionIsProperSymbolReference(node.expression, links.resolvedType, /*reportError*/ true); } } return links.resolvedType; } function isSymbolWithNumericName(symbol: Symbol) { const firstDecl = symbol.declarations?.[0]; return isNumericLiteralName(symbol.escapedName) || (firstDecl && isNamedDeclaration(firstDecl) && isNumericName(firstDecl.name)); } function getObjectLiteralIndexInfo(node: ObjectLiteralExpression, offset: number, properties: Symbol[], kind: IndexKind): IndexInfo { const propTypes: Type[] = []; for (let i = offset; i < properties.length; i++) { if (kind === IndexKind.String || isSymbolWithNumericName(properties[i])) { propTypes.push(getTypeOfSymbol(properties[i])); } } const unionType = propTypes.length ? getUnionType(propTypes, UnionReduction.Subtype) : undefinedType; return createIndexInfo(unionType, isConstContext(node)); } function getImmediateAliasedSymbol(symbol: Symbol): Symbol | undefined { Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here."); const links = getSymbolLinks(symbol); if (!links.immediateTarget) { const node = getDeclarationOfAliasSymbol(symbol); if (!node) return Debug.fail(); links.immediateTarget = getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/ true); } return links.immediateTarget; } function checkObjectLiteral(node: ObjectLiteralExpression, checkMode?: CheckMode): Type { const inDestructuringPattern = isAssignmentTarget(node); // Grammar checking checkGrammarObjectLiteralExpression(node, inDestructuringPattern); const allPropertiesTable = strictNullChecks ? createSymbolTable() : undefined; let propertiesTable = createSymbolTable(); let propertiesArray: Symbol[] = []; let spread: Type = emptyObjectType; const contextualType = getApparentTypeOfContextualType(node); const contextualTypeHasPattern = contextualType && contextualType.pattern && (contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression); const inConstContext = isConstContext(node); const checkFlags = inConstContext ? CheckFlags.Readonly : 0; const isInJavascript = isInJSFile(node) && !isInJsonFile(node); const enumTag = getJSDocEnumTag(node); const isJSObjectLiteral = !contextualType && isInJavascript && !enumTag; let objectFlags: ObjectFlags = freshObjectLiteralFlag; let patternWithComputedProperties = false; let hasComputedStringProperty = false; let hasComputedNumberProperty = false; // Spreads may cause an early bail; ensure computed names are always checked (this is cached) // As otherwise they may not be checked until exports for the type at this position are retrieved, // which may never occur. for (const elem of node.properties) { if (elem.name && isComputedPropertyName(elem.name) && !isWellKnownSymbolSyntactically(elem.name)) { checkComputedPropertyName(elem.name); } } let offset = 0; for (const memberDecl of node.properties) { let member = getSymbolOfNode(memberDecl); const computedNameType = memberDecl.name && memberDecl.name.kind === SyntaxKind.ComputedPropertyName && !isWellKnownSymbolSyntactically(memberDecl.name.expression) ? checkComputedPropertyName(memberDecl.name) : undefined; if (memberDecl.kind === SyntaxKind.PropertyAssignment || memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment || isObjectLiteralMethod(memberDecl)) { let type = memberDecl.kind === SyntaxKind.PropertyAssignment ? checkPropertyAssignment(memberDecl, checkMode) : // avoid resolving the left side of the ShorthandPropertyAssignment outside of the destructuring // for error recovery purposes. For example, if a user wrote `{ a = 100 }` instead of `{ a: 100 }`. // we don't want to say "could not find 'a'". memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment ? checkExpressionForMutableLocation(!inDestructuringPattern && memberDecl.objectAssignmentInitializer ? memberDecl.objectAssignmentInitializer : memberDecl.name, checkMode) : checkObjectLiteralMethod(memberDecl, checkMode); if (isInJavascript) { const jsDocType = getTypeForDeclarationFromJSDocComment(memberDecl); if (jsDocType) { checkTypeAssignableTo(type, jsDocType, memberDecl); type = jsDocType; } else if (enumTag && enumTag.typeExpression) { checkTypeAssignableTo(type, getTypeFromTypeNode(enumTag.typeExpression), memberDecl); } } objectFlags |= getObjectFlags(type) & ObjectFlags.PropagatingFlags; const nameType = computedNameType && isTypeUsableAsPropertyName(computedNameType) ? computedNameType : undefined; const prop = nameType ? createSymbol(SymbolFlags.Property | member.flags, getPropertyNameFromType(nameType), checkFlags | CheckFlags.Late) : createSymbol(SymbolFlags.Property | member.flags, member.escapedName, checkFlags); if (nameType) { prop.nameType = nameType; } if (inDestructuringPattern) { // If object literal is an assignment pattern and if the assignment pattern specifies a default value // for the property, make the property optional. const isOptional = (memberDecl.kind === SyntaxKind.PropertyAssignment && hasDefaultValue(memberDecl.initializer)) || (memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment && memberDecl.objectAssignmentInitializer); if (isOptional) { prop.flags |= SymbolFlags.Optional; } } else if (contextualTypeHasPattern && !(getObjectFlags(contextualType!) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) { // If object literal is contextually typed by the implied type of a binding pattern, and if the // binding pattern specifies a default value for the property, make the property optional. const impliedProp = getPropertyOfType(contextualType!, member.escapedName); if (impliedProp) { prop.flags |= impliedProp.flags & SymbolFlags.Optional; } else if (!compilerOptions.suppressExcessPropertyErrors && !getIndexInfoOfType(contextualType!, IndexKind.String)) { error(memberDecl.name, Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, symbolToString(member), typeToString(contextualType!)); } } prop.declarations = member.declarations; prop.parent = member.parent; if (member.valueDeclaration) { prop.valueDeclaration = member.valueDeclaration; } prop.type = type; prop.target = member; member = prop; allPropertiesTable?.set(prop.escapedName, prop); } else if (memberDecl.kind === SyntaxKind.SpreadAssignment) { if (languageVersion < ScriptTarget.ES2015) { checkExternalEmitHelpers(memberDecl, ExternalEmitHelpers.Assign); } if (propertiesArray.length > 0) { spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext); propertiesArray = []; propertiesTable = createSymbolTable(); hasComputedStringProperty = false; hasComputedNumberProperty = false; } const type = getReducedType(checkExpression(memberDecl.expression)); if (!isValidSpreadType(type)) { error(memberDecl, Diagnostics.Spread_types_may_only_be_created_from_object_types); return errorType; } if (allPropertiesTable) { checkSpreadPropOverrides(type, allPropertiesTable, memberDecl); } spread = getSpreadType(spread, type, node.symbol, objectFlags, inConstContext); offset = propertiesArray.length; continue; } else { // TypeScript 1.0 spec (April 2014) // A get accessor declaration is processed in the same manner as // an ordinary function declaration(section 6.1) with no parameters. // A set accessor declaration is processed in the same manner // as an ordinary function declaration with a single parameter and a Void return type. Debug.assert(memberDecl.kind === SyntaxKind.GetAccessor || memberDecl.kind === SyntaxKind.SetAccessor); checkNodeDeferred(memberDecl); } if (computedNameType && !(computedNameType.flags & TypeFlags.StringOrNumberLiteralOrUnique)) { if (isTypeAssignableTo(computedNameType, stringNumberSymbolType)) { if (isTypeAssignableTo(computedNameType, numberType)) { hasComputedNumberProperty = true; } else { hasComputedStringProperty = true; } if (inDestructuringPattern) { patternWithComputedProperties = true; } } } else { propertiesTable.set(member.escapedName, member); } propertiesArray.push(member); } // If object literal is contextually typed by the implied type of a binding pattern, augment the result // type with those properties for which the binding pattern specifies a default value. // If the object literal is spread into another object literal, skip this step and let the top-level object // literal handle it instead. if (contextualTypeHasPattern && node.parent.kind !== SyntaxKind.SpreadAssignment) { for (const prop of getPropertiesOfType(contextualType!)) { if (!propertiesTable.get(prop.escapedName) && !getPropertyOfType(spread, prop.escapedName)) { if (!(prop.flags & SymbolFlags.Optional)) { error(prop.valueDeclaration || (prop).bindingElement, Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value); } propertiesTable.set(prop.escapedName, prop); propertiesArray.push(prop); } } } if (spread !== emptyObjectType) { if (propertiesArray.length > 0) { spread = getSpreadType(spread, createObjectLiteralType(), node.symbol, objectFlags, inConstContext); propertiesArray = []; propertiesTable = createSymbolTable(); hasComputedStringProperty = false; hasComputedNumberProperty = false; } // remap the raw emptyObjectType fed in at the top into a fresh empty object literal type, unique to this use site return mapType(spread, t => t === emptyObjectType ? createObjectLiteralType() : t); } return createObjectLiteralType(); function createObjectLiteralType() { const stringIndexInfo = hasComputedStringProperty ? getObjectLiteralIndexInfo(node, offset, propertiesArray, IndexKind.String) : undefined; const numberIndexInfo = hasComputedNumberProperty ? getObjectLiteralIndexInfo(node, offset, propertiesArray, IndexKind.Number) : undefined; const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; if (isJSObjectLiteral) { result.objectFlags |= ObjectFlags.JSLiteral; } if (patternWithComputedProperties) { result.objectFlags |= ObjectFlags.ObjectLiteralPatternWithComputedProperties; } if (inDestructuringPattern) { result.pattern = node; } return result; } } function isValidSpreadType(type: Type): boolean { if (type.flags & TypeFlags.Instantiable) { const constraint = getBaseConstraintOfType(type); if (constraint !== undefined) { return isValidSpreadType(constraint); } } return !!(type.flags & (TypeFlags.Any | TypeFlags.NonPrimitive | TypeFlags.Object | TypeFlags.InstantiableNonPrimitive) || getFalsyFlags(type) & TypeFlags.DefinitelyFalsy && isValidSpreadType(removeDefinitelyFalsyTypes(type)) || type.flags & TypeFlags.UnionOrIntersection && every((type).types, isValidSpreadType)); } function checkJsxSelfClosingElementDeferred(node: JsxSelfClosingElement) { checkJsxOpeningLikeElementOrOpeningFragment(node); } function checkJsxSelfClosingElement(node: JsxSelfClosingElement, _checkMode: CheckMode | undefined): Type { checkNodeDeferred(node); return getJsxElementTypeAt(node) || anyType; } function checkJsxElementDeferred(node: JsxElement) { // Check attributes checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement); // Perform resolution on the closing tag so that rename/go to definition/etc work if (isJsxIntrinsicIdentifier(node.closingElement.tagName)) { getIntrinsicTagSymbol(node.closingElement); } else { checkExpression(node.closingElement.tagName); } checkJsxChildren(node); } function checkJsxElement(node: JsxElement, _checkMode: CheckMode | undefined): Type { checkNodeDeferred(node); return getJsxElementTypeAt(node) || anyType; } function checkJsxFragment(node: JsxFragment): Type { checkJsxOpeningLikeElementOrOpeningFragment(node.openingFragment); // by default, jsx:'react' will use jsxFactory = React.createElement and jsxFragmentFactory = React.Fragment // if jsxFactory compiler option is provided, ensure jsxFragmentFactory compiler option or @jsxFrag pragma is provided too const nodeSourceFile = getSourceFileOfNode(node); if (getJSXTransformEnabled(compilerOptions) && (compilerOptions.jsxFactory || nodeSourceFile.pragmas.has("jsx")) && !compilerOptions.jsxFragmentFactory && !nodeSourceFile.pragmas.has("jsxfrag")) { error(node, compilerOptions.jsxFactory ? Diagnostics.The_jsxFragmentFactory_compiler_option_must_be_provided_to_use_JSX_fragments_with_the_jsxFactory_compiler_option : Diagnostics.An_jsxFrag_pragma_is_required_when_using_an_jsx_pragma_with_JSX_fragments); } checkJsxChildren(node); return getJsxElementTypeAt(node) || anyType; } /** * Returns true iff the JSX element name would be a valid JS identifier, ignoring restrictions about keywords not being identifiers */ function isUnhyphenatedJsxName(name: string | __String) { // - is the only character supported in JSX attribute names that isn't valid in JavaScript identifiers return !stringContains(name as string, "-"); } /** * Returns true iff React would emit this tag name as a string rather than an identifier or qualified name */ function isJsxIntrinsicIdentifier(tagName: JsxTagNameExpression): boolean { return tagName.kind === SyntaxKind.Identifier && isIntrinsicJsxName(tagName.escapedText); } function checkJsxAttribute(node: JsxAttribute, checkMode?: CheckMode) { return node.initializer ? checkExpressionForMutableLocation(node.initializer, checkMode) : trueType; // is sugar for } /** * Get attributes type of the JSX opening-like element. The result is from resolving "attributes" property of the opening-like element. * * @param openingLikeElement a JSX opening-like element * @param filter a function to remove attributes that will not participate in checking whether attributes are assignable * @return an anonymous type (similar to the one returned by checkObjectLiteral) in which its properties are attributes property. * @remarks Because this function calls getSpreadType, it needs to use the same checks as checkObjectLiteral, * which also calls getSpreadType. */ function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, checkMode: CheckMode | undefined) { const attributes = openingLikeElement.attributes; const allAttributesTable = strictNullChecks ? createSymbolTable() : undefined; let attributesTable = createSymbolTable(); let spread: Type = emptyJsxObjectType; let hasSpreadAnyType = false; let typeToIntersect: Type | undefined; let explicitlySpecifyChildrenAttribute = false; let objectFlags: ObjectFlags = ObjectFlags.JsxAttributes; const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(openingLikeElement)); for (const attributeDecl of attributes.properties) { const member = attributeDecl.symbol; if (isJsxAttribute(attributeDecl)) { const exprType = checkJsxAttribute(attributeDecl, checkMode); objectFlags |= getObjectFlags(exprType) & ObjectFlags.PropagatingFlags; const attributeSymbol = createSymbol(SymbolFlags.Property | member.flags, member.escapedName); attributeSymbol.declarations = member.declarations; attributeSymbol.parent = member.parent; if (member.valueDeclaration) { attributeSymbol.valueDeclaration = member.valueDeclaration; } attributeSymbol.type = exprType; attributeSymbol.target = member; attributesTable.set(attributeSymbol.escapedName, attributeSymbol); allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol); if (attributeDecl.name.escapedText === jsxChildrenPropertyName) { explicitlySpecifyChildrenAttribute = true; } } else { Debug.assert(attributeDecl.kind === SyntaxKind.JsxSpreadAttribute); if (attributesTable.size > 0) { spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); attributesTable = createSymbolTable(); } const exprType = getReducedType(checkExpressionCached(attributeDecl.expression, checkMode)); if (isTypeAny(exprType)) { hasSpreadAnyType = true; } if (isValidSpreadType(exprType)) { spread = getSpreadType(spread, exprType, attributes.symbol, objectFlags, /*readonly*/ false); if (allAttributesTable) { checkSpreadPropOverrides(exprType, allAttributesTable, attributeDecl); } } else { typeToIntersect = typeToIntersect ? getIntersectionType([typeToIntersect, exprType]) : exprType; } } } if (!hasSpreadAnyType) { if (attributesTable.size > 0) { spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false); } } // Handle children attribute const parent = openingLikeElement.parent.kind === SyntaxKind.JsxElement ? openingLikeElement.parent as JsxElement : undefined; // We have to check that openingElement of the parent is the one we are visiting as this may not be true for selfClosingElement if (parent && parent.openingElement === openingLikeElement && parent.children.length > 0) { const childrenTypes: Type[] = checkJsxChildren(parent, checkMode); if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") { // Error if there is a attribute named "children" explicitly specified and children element. // This is because children element will overwrite the value from attributes. // Note: we will not warn "children" attribute overwritten if "children" attribute is specified in object spread. if (explicitlySpecifyChildrenAttribute) { error(attributes, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, unescapeLeadingUnderscores(jsxChildrenPropertyName)); } const contextualType = getApparentTypeOfContextualType(openingLikeElement.attributes); const childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName); // If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process const childrenPropSymbol = createSymbol(SymbolFlags.Property, jsxChildrenPropertyName); childrenPropSymbol.type = childrenTypes.length === 1 ? childrenTypes[0] : childrenContextualType && forEachType(childrenContextualType, isTupleLikeType) ? createTupleType(childrenTypes) : createArrayType(getUnionType(childrenTypes)); // Fake up a property declaration for the children childrenPropSymbol.valueDeclaration = factory.createPropertySignature(/*modifiers*/ undefined, unescapeLeadingUnderscores(jsxChildrenPropertyName), /*questionToken*/ undefined, /*type*/ undefined); setParent(childrenPropSymbol.valueDeclaration, attributes); childrenPropSymbol.valueDeclaration.symbol = childrenPropSymbol; const childPropMap = createSymbolTable(); childPropMap.set(jsxChildrenPropertyName, childrenPropSymbol); spread = getSpreadType(spread, createAnonymousType(attributes.symbol, childPropMap, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined), attributes.symbol, objectFlags, /*readonly*/ false); } } if (hasSpreadAnyType) { return anyType; } if (typeToIntersect && spread !== emptyJsxObjectType) { return getIntersectionType([typeToIntersect, spread]); } return typeToIntersect || (spread === emptyJsxObjectType ? createJsxAttributesType() : spread); /** * Create anonymous type from given attributes symbol table. * @param symbol a symbol of JsxAttributes containing attributes corresponding to attributesTable * @param attributesTable a symbol table of attributes property */ function createJsxAttributesType() { objectFlags |= freshObjectLiteralFlag; const result = createAnonymousType(attributes.symbol, attributesTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined); result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; return result; } } function checkJsxChildren(node: JsxElement | JsxFragment, checkMode?: CheckMode) { const childrenTypes: Type[] = []; for (const child of node.children) { // In React, JSX text that contains only whitespaces will be ignored so we don't want to type-check that // because then type of children property will have constituent of string type. if (child.kind === SyntaxKind.JsxText) { if (!child.containsOnlyTriviaWhiteSpaces) { childrenTypes.push(stringType); } } else if (child.kind === SyntaxKind.JsxExpression && !child.expression) { continue; // empty jsx expressions don't *really* count as present children } else { childrenTypes.push(checkExpressionForMutableLocation(child, checkMode)); } } return childrenTypes; } function checkSpreadPropOverrides(type: Type, props: SymbolTable, spread: SpreadAssignment | JsxSpreadAttribute) { for (const right of getPropertiesOfType(type)) { const left = props.get(right.escapedName); const rightType = getTypeOfSymbol(right); if (left && !maybeTypeOfKind(rightType, TypeFlags.Nullable) && !(maybeTypeOfKind(rightType, TypeFlags.AnyOrUnknown) && right.flags & SymbolFlags.Optional)) { const diagnostic = error(left.valueDeclaration, Diagnostics._0_is_specified_more_than_once_so_this_usage_will_be_overwritten, unescapeLeadingUnderscores(left.escapedName)); addRelatedInfo(diagnostic, createDiagnosticForNode(spread, Diagnostics.This_spread_always_overwrites_this_property)); } } } /** * Check attributes property of opening-like element. This function is called during chooseOverload to get call signature of a JSX opening-like element. * (See "checkApplicableSignatureForJsxOpeningLikeElement" for how the function is used) * @param node a JSXAttributes to be resolved of its type */ function checkJsxAttributes(node: JsxAttributes, checkMode: CheckMode | undefined) { return createJsxAttributesTypeFromAttributesProperty(node.parent, checkMode); } function getJsxType(name: __String, location: Node | undefined) { const namespace = getJsxNamespaceAt(location); const exports = namespace && getExportsOfSymbol(namespace); const typeSymbol = exports && getSymbol(exports, name, SymbolFlags.Type); return typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType; } /** * Looks up an intrinsic tag name and returns a symbol that either points to an intrinsic * property (in which case nodeLinks.jsxFlags will be IntrinsicNamedElement) or an intrinsic * string index signature (in which case nodeLinks.jsxFlags will be IntrinsicIndexedElement). * May also return unknownSymbol if both of these lookups fail. */ function getIntrinsicTagSymbol(node: JsxOpeningLikeElement | JsxClosingElement): Symbol { const links = getNodeLinks(node); if (!links.resolvedSymbol) { const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, node); if (intrinsicElementsType !== errorType) { // Property case if (!isIdentifier(node.tagName)) return Debug.fail(); const intrinsicProp = getPropertyOfType(intrinsicElementsType, node.tagName.escapedText); if (intrinsicProp) { links.jsxFlags |= JsxFlags.IntrinsicNamedElement; return links.resolvedSymbol = intrinsicProp; } // Intrinsic string indexer case const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, IndexKind.String); if (indexSignatureType) { links.jsxFlags |= JsxFlags.IntrinsicIndexedElement; return links.resolvedSymbol = intrinsicElementsType.symbol; } // Wasn't found error(node, Diagnostics.Property_0_does_not_exist_on_type_1, idText(node.tagName), "JSX." + JsxNames.IntrinsicElements); return links.resolvedSymbol = unknownSymbol; } else { if (noImplicitAny) { error(node, Diagnostics.JSX_element_implicitly_has_type_any_because_no_interface_JSX_0_exists, unescapeLeadingUnderscores(JsxNames.IntrinsicElements)); } return links.resolvedSymbol = unknownSymbol; } } return links.resolvedSymbol; } function getJsxNamespaceContainerForImplicitImport(location: Node | undefined): Symbol | undefined { const file = location && getSourceFileOfNode(location); const links = file && getNodeLinks(file); if (links && links.jsxImplicitImportContainer === false) { return undefined; } if (links && links.jsxImplicitImportContainer) { return links.jsxImplicitImportContainer; } const runtimeImportSpecifier = getJSXRuntimeImport(getJSXImplicitImportBase(compilerOptions, file), compilerOptions); if (!runtimeImportSpecifier) { return undefined; } const isClassic = getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Classic; const errorMessage = isClassic ? Diagnostics.Cannot_find_module_0_Did_you_mean_to_set_the_moduleResolution_option_to_node_or_to_add_aliases_to_the_paths_option : Diagnostics.Cannot_find_module_0_or_its_corresponding_type_declarations; const mod = resolveExternalModule(location!, runtimeImportSpecifier, errorMessage, location!); const result = mod && mod !== unknownSymbol ? getMergedSymbol(resolveSymbol(mod)) : undefined; if (links) { links.jsxImplicitImportContainer = result || false; } return result; } function getJsxNamespaceAt(location: Node | undefined): Symbol { const links = location && getNodeLinks(location); if (links && links.jsxNamespace) { return links.jsxNamespace; } if (!links || links.jsxNamespace !== false) { let resolvedNamespace = getJsxNamespaceContainerForImplicitImport(location); if (!resolvedNamespace || resolvedNamespace === unknownSymbol) { const namespaceName = getJsxNamespace(location); resolvedNamespace = resolveName(location, namespaceName, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined, namespaceName, /*isUse*/ false); } if (resolvedNamespace) { const candidate = resolveSymbol(getSymbol(getExportsOfSymbol(resolveSymbol(resolvedNamespace)), JsxNames.JSX, SymbolFlags.Namespace)); if (candidate && candidate !== unknownSymbol) { if (links) { links.jsxNamespace = candidate; } return candidate; } } if (links) { links.jsxNamespace = false; } } // JSX global fallback const s = resolveSymbol(getGlobalSymbol(JsxNames.JSX, SymbolFlags.Namespace, /*diagnosticMessage*/ undefined)); if (s === unknownSymbol) { return undefined!; // TODO: GH#18217 } return s!; // TODO: GH#18217 } /** * Look into JSX namespace and then look for container with matching name as nameOfAttribPropContainer. * Get a single property from that container if existed. Report an error if there are more than one property. * * @param nameOfAttribPropContainer a string of value JsxNames.ElementAttributesPropertyNameContainer or JsxNames.ElementChildrenAttributeNameContainer * if other string is given or the container doesn't exist, return undefined. */ function getNameFromJsxElementAttributesContainer(nameOfAttribPropContainer: __String, jsxNamespace: Symbol): __String | undefined { // JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [symbol] const jsxElementAttribPropInterfaceSym = jsxNamespace && getSymbol(jsxNamespace.exports!, nameOfAttribPropContainer, SymbolFlags.Type); // JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute [type] const jsxElementAttribPropInterfaceType = jsxElementAttribPropInterfaceSym && getDeclaredTypeOfSymbol(jsxElementAttribPropInterfaceSym); // The properties of JSX.ElementAttributesProperty | JSX.ElementChildrenAttribute const propertiesOfJsxElementAttribPropInterface = jsxElementAttribPropInterfaceType && getPropertiesOfType(jsxElementAttribPropInterfaceType); if (propertiesOfJsxElementAttribPropInterface) { // Element Attributes has zero properties, so the element attributes type will be the class instance type if (propertiesOfJsxElementAttribPropInterface.length === 0) { return "" as __String; } // Element Attributes has one property, so the element attributes type will be the type of the corresponding // property of the class instance type else if (propertiesOfJsxElementAttribPropInterface.length === 1) { return propertiesOfJsxElementAttribPropInterface[0].escapedName; } else if (propertiesOfJsxElementAttribPropInterface.length > 1) { // More than one property on ElementAttributesProperty is an error error(jsxElementAttribPropInterfaceSym!.declarations[0], Diagnostics.The_global_type_JSX_0_may_not_have_more_than_one_property, unescapeLeadingUnderscores(nameOfAttribPropContainer)); } } return undefined; } function getJsxLibraryManagedAttributes(jsxNamespace: Symbol) { // JSX.LibraryManagedAttributes [symbol] return jsxNamespace && getSymbol(jsxNamespace.exports!, JsxNames.LibraryManagedAttributes, SymbolFlags.Type); } /// e.g. "props" for React.d.ts, /// or 'undefined' if ElementAttributesProperty doesn't exist (which means all /// non-intrinsic elements' attributes type is 'any'), /// or '' if it has 0 properties (which means every /// non-intrinsic elements' attributes type is the element instance type) function getJsxElementPropertiesName(jsxNamespace: Symbol) { return getNameFromJsxElementAttributesContainer(JsxNames.ElementAttributesPropertyNameContainer, jsxNamespace); } function getJsxElementChildrenPropertyName(jsxNamespace: Symbol): __String | undefined { return getNameFromJsxElementAttributesContainer(JsxNames.ElementChildrenAttributeNameContainer, jsxNamespace); } function getUninstantiatedJsxSignaturesOfType(elementType: Type, caller: JsxOpeningLikeElement): readonly Signature[] { if (elementType.flags & TypeFlags.String) { return [anySignature]; } else if (elementType.flags & TypeFlags.StringLiteral) { const intrinsicType = getIntrinsicAttributesTypeFromStringLiteralType(elementType as StringLiteralType, caller); if (!intrinsicType) { error(caller, Diagnostics.Property_0_does_not_exist_on_type_1, (elementType as StringLiteralType).value, "JSX." + JsxNames.IntrinsicElements); return emptyArray; } else { const fakeSignature = createSignatureForJSXIntrinsic(caller, intrinsicType); return [fakeSignature]; } } const apparentElemType = getApparentType(elementType); // Resolve the signatures, preferring constructor let signatures = getSignaturesOfType(apparentElemType, SignatureKind.Construct); if (signatures.length === 0) { // No construct signatures, try call signatures signatures = getSignaturesOfType(apparentElemType, SignatureKind.Call); } if (signatures.length === 0 && apparentElemType.flags & TypeFlags.Union) { // If each member has some combination of new/call signatures; make a union signature list for those signatures = getUnionSignatures(map((apparentElemType as UnionType).types, t => getUninstantiatedJsxSignaturesOfType(t, caller))); } return signatures; } function getIntrinsicAttributesTypeFromStringLiteralType(type: StringLiteralType, location: Node): Type | undefined { // If the elemType is a stringLiteral type, we can then provide a check to make sure that the string literal type is one of the Jsx intrinsic element type // For example: // var CustomTag: "h1" = "h1"; // Hello World const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, location); if (intrinsicElementsType !== errorType) { const stringLiteralTypeName = type.value; const intrinsicProp = getPropertyOfType(intrinsicElementsType, escapeLeadingUnderscores(stringLiteralTypeName)); if (intrinsicProp) { return getTypeOfSymbol(intrinsicProp); } const indexSignatureType = getIndexTypeOfType(intrinsicElementsType, IndexKind.String); if (indexSignatureType) { return indexSignatureType; } return undefined; } // If we need to report an error, we already done so here. So just return any to prevent any more error downstream return anyType; } function checkJsxReturnAssignableToAppropriateBound(refKind: JsxReferenceKind, elemInstanceType: Type, openingLikeElement: JsxOpeningLikeElement) { if (refKind === JsxReferenceKind.Function) { const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement); if (sfcReturnConstraint) { checkTypeRelatedTo(elemInstanceType, sfcReturnConstraint, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_return_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain); } } else if (refKind === JsxReferenceKind.Component) { const classConstraint = getJsxElementClassTypeAt(openingLikeElement); if (classConstraint) { // Issue an error if this return type isn't assignable to JSX.ElementClass, failing that checkTypeRelatedTo(elemInstanceType, classConstraint, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_instance_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain); } } else { // Mixed const sfcReturnConstraint = getJsxStatelessElementTypeAt(openingLikeElement); const classConstraint = getJsxElementClassTypeAt(openingLikeElement); if (!sfcReturnConstraint || !classConstraint) { return; } const combined = getUnionType([sfcReturnConstraint, classConstraint]); checkTypeRelatedTo(elemInstanceType, combined, assignableRelation, openingLikeElement.tagName, Diagnostics.Its_element_type_0_is_not_a_valid_JSX_element, generateInitialErrorChain); } function generateInitialErrorChain(): DiagnosticMessageChain { const componentName = getTextOfNode(openingLikeElement.tagName); return chainDiagnosticMessages(/* details */ undefined, Diagnostics._0_cannot_be_used_as_a_JSX_component, componentName); } } /** * Get attributes type of the given intrinsic opening-like Jsx element by resolving the tag name. * The function is intended to be called from a function which has checked that the opening element is an intrinsic element. * @param node an intrinsic JSX opening-like element */ function getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node: JsxOpeningLikeElement): Type { Debug.assert(isJsxIntrinsicIdentifier(node.tagName)); const links = getNodeLinks(node); if (!links.resolvedJsxElementAttributesType) { const symbol = getIntrinsicTagSymbol(node); if (links.jsxFlags & JsxFlags.IntrinsicNamedElement) { return links.resolvedJsxElementAttributesType = getTypeOfSymbol(symbol); } else if (links.jsxFlags & JsxFlags.IntrinsicIndexedElement) { return links.resolvedJsxElementAttributesType = getIndexTypeOfType(getDeclaredTypeOfSymbol(symbol), IndexKind.String)!; } else { return links.resolvedJsxElementAttributesType = errorType; } } return links.resolvedJsxElementAttributesType; } function getJsxElementClassTypeAt(location: Node): Type | undefined { const type = getJsxType(JsxNames.ElementClass, location); if (type === errorType) return undefined; return type; } function getJsxElementTypeAt(location: Node): Type { return getJsxType(JsxNames.Element, location); } function getJsxStatelessElementTypeAt(location: Node): Type | undefined { const jsxElementType = getJsxElementTypeAt(location); if (jsxElementType) { return getUnionType([jsxElementType, nullType]); } } /** * Returns all the properties of the Jsx.IntrinsicElements interface */ function getJsxIntrinsicTagNamesAt(location: Node): Symbol[] { const intrinsics = getJsxType(JsxNames.IntrinsicElements, location); return intrinsics ? getPropertiesOfType(intrinsics) : emptyArray; } function checkJsxPreconditions(errorNode: Node) { // Preconditions for using JSX if ((compilerOptions.jsx || JsxEmit.None) === JsxEmit.None) { error(errorNode, Diagnostics.Cannot_use_JSX_unless_the_jsx_flag_is_provided); } if (getJsxElementTypeAt(errorNode) === undefined) { if (noImplicitAny) { error(errorNode, Diagnostics.JSX_element_implicitly_has_type_any_because_the_global_type_JSX_Element_does_not_exist); } } } function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) { const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node); if (isNodeOpeningLikeElement) { checkGrammarJsxElement(node); } checkJsxPreconditions(node); if (!getJsxNamespaceContainerForImplicitImport(node)) { // The reactNamespace/jsxFactory's root symbol should be marked as 'used' so we don't incorrectly elide its import. // And if there is no reactNamespace/jsxFactory's symbol in scope when targeting React emit, we should issue an error. const jsxFactoryRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined; const jsxFactoryNamespace = getJsxNamespace(node); const jsxFactoryLocation = isNodeOpeningLikeElement ? (node).tagName : node; // allow null as jsxFragmentFactory let jsxFactorySym: Symbol | undefined; if (!(isJsxOpeningFragment(node) && jsxFactoryNamespace === "null")) { jsxFactorySym = resolveName(jsxFactoryLocation, jsxFactoryNamespace, SymbolFlags.Value, jsxFactoryRefErr, jsxFactoryNamespace, /*isUse*/ true); } if (jsxFactorySym) { // Mark local symbol as referenced here because it might not have been marked // if jsx emit was not jsxFactory as there wont be error being emitted jsxFactorySym.isReferenced = SymbolFlags.All; // If react/jsxFactory symbol is alias, mark it as refereced if (jsxFactorySym.flags & SymbolFlags.Alias && !getTypeOnlyAliasDeclaration(jsxFactorySym)) { markAliasSymbolAsReferenced(jsxFactorySym); } } } if (isNodeOpeningLikeElement) { const jsxOpeningLikeNode = node as JsxOpeningLikeElement; const sig = getResolvedSignature(jsxOpeningLikeNode); checkDeprecatedSignature(sig, node); checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode); } } /** * Check if a property with the given name is known anywhere in the given type. In an object type, a property * is considered known if * 1. the object type is empty and the check is for assignability, or * 2. if the object type has index signatures, or * 3. if the property is actually declared in the object type * (this means that 'toString', for example, is not usually a known property). * 4. In a union or intersection type, * a property is considered known if it is known in any constituent type. * @param targetType a type to search a given name in * @param name a property name to search * @param isComparingJsxAttributes a boolean flag indicating whether we are searching in JsxAttributesType */ function isKnownProperty(targetType: Type, name: __String, isComparingJsxAttributes: boolean): boolean { if (targetType.flags & TypeFlags.Object) { const resolved = resolveStructuredTypeMembers(targetType as ObjectType); if (resolved.stringIndexInfo || resolved.numberIndexInfo && isNumericLiteralName(name) || getPropertyOfObjectType(targetType, name) || isComparingJsxAttributes && !isUnhyphenatedJsxName(name)) { // For JSXAttributes, if the attribute has a hyphenated name, consider that the attribute to be known. return true; } } else if (targetType.flags & TypeFlags.UnionOrIntersection && isExcessPropertyCheckTarget(targetType)) { for (const t of (targetType as UnionOrIntersectionType).types) { if (isKnownProperty(t, name, isComparingJsxAttributes)) { return true; } } } return false; } function isExcessPropertyCheckTarget(type: Type): boolean { return !!(type.flags & TypeFlags.Object && !(getObjectFlags(type) & ObjectFlags.ObjectLiteralPatternWithComputedProperties) || type.flags & TypeFlags.NonPrimitive || type.flags & TypeFlags.Union && some((type).types, isExcessPropertyCheckTarget) || type.flags & TypeFlags.Intersection && every((type).types, isExcessPropertyCheckTarget)); } function checkJsxExpression(node: JsxExpression, checkMode?: CheckMode) { checkGrammarJsxExpression(node); if (node.expression) { const type = checkExpression(node.expression, checkMode); if (node.dotDotDotToken && type !== anyType && !isArrayType(type)) { error(node, Diagnostics.JSX_spread_child_must_be_an_array_type); } return type; } else { return errorType; } } function getDeclarationNodeFlagsFromSymbol(s: Symbol): NodeFlags { return s.valueDeclaration ? getCombinedNodeFlags(s.valueDeclaration) : 0; } /** * Return whether this symbol is a member of a prototype somewhere * Note that this is not tracked well within the compiler, so the answer may be incorrect. */ function isPrototypeProperty(symbol: Symbol) { if (symbol.flags & SymbolFlags.Method || getCheckFlags(symbol) & CheckFlags.SyntheticMethod) { return true; } if (isInJSFile(symbol.valueDeclaration)) { const parent = symbol.valueDeclaration.parent; return parent && isBinaryExpression(parent) && getAssignmentDeclarationKind(parent) === AssignmentDeclarationKind.PrototypeProperty; } } /** * Check whether the requested property access is valid. * Returns true if node is a valid property access, and false otherwise. * @param node The node to be checked. * @param isSuper True if the access is from `super.`. * @param type The type of the object whose property is being accessed. (Not the type of the property.) * @param prop The symbol for the property being accessed. */ function checkPropertyAccessibility( node: PropertyAccessExpression | QualifiedName | PropertyAccessExpression | VariableDeclaration | ParameterDeclaration | ImportTypeNode | PropertyAssignment | ShorthandPropertyAssignment | BindingElement, isSuper: boolean, type: Type, prop: Symbol): boolean { const flags = getDeclarationModifierFlagsFromSymbol(prop); const errorNode = node.kind === SyntaxKind.QualifiedName ? node.right : node.kind === SyntaxKind.ImportType ? node : node.name; if (isSuper) { // TS 1.0 spec (April 2014): 4.8.2 // - In a constructor, instance member function, instance member accessor, or // instance member variable initializer where this references a derived class instance, // a super property access is permitted and must specify a public instance member function of the base class. // - In a static member function or static member accessor // where this references the constructor function object of a derived class, // a super property access is permitted and must specify a public static member function of the base class. if (languageVersion < ScriptTarget.ES2015) { if (symbolHasNonMethodDeclaration(prop)) { error(errorNode, Diagnostics.Only_public_and_protected_methods_of_the_base_class_are_accessible_via_the_super_keyword); return false; } } if (flags & ModifierFlags.Abstract) { // A method cannot be accessed in a super property access if the method is abstract. // This error could mask a private property access error. But, a member // cannot simultaneously be private and abstract, so this will trigger an // additional error elsewhere. error(errorNode, Diagnostics.Abstract_method_0_in_class_1_cannot_be_accessed_via_super_expression, symbolToString(prop), typeToString(getDeclaringClass(prop)!)); return false; } } // Referencing abstract properties within their own constructors is not allowed if ((flags & ModifierFlags.Abstract) && isThisProperty(node) && symbolHasNonMethodDeclaration(prop)) { const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!); if (declaringClassDeclaration && isNodeUsedDuringClassInitialization(node)) { error(errorNode, Diagnostics.Abstract_property_0_in_class_1_cannot_be_accessed_in_the_constructor, symbolToString(prop), getTextOfIdentifierOrLiteral(declaringClassDeclaration.name!)); // TODO: GH#18217 return false; } } if (isPropertyAccessExpression(node) && isPrivateIdentifier(node.name)) { if (!getContainingClass(node)) { error(errorNode, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); return false; } return true; } // Public properties are otherwise accessible. if (!(flags & ModifierFlags.NonPublicAccessibilityModifier)) { return true; } // Property is known to be private or protected at this point // Private property is accessible if the property is within the declaring class if (flags & ModifierFlags.Private) { const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(getParentOfSymbol(prop)!)!; if (!isNodeWithinClass(node, declaringClassDeclaration)) { error(errorNode, Diagnostics.Property_0_is_private_and_only_accessible_within_class_1, symbolToString(prop), typeToString(getDeclaringClass(prop)!)); return false; } return true; } // Property is known to be protected at this point // All protected properties of a supertype are accessible in a super access if (isSuper) { return true; } // Find the first enclosing class that has the declaring classes of the protected constituents // of the property as base classes let enclosingClass = forEachEnclosingClass(node, enclosingDeclaration => { const enclosingClass = getDeclaredTypeOfSymbol(getSymbolOfNode(enclosingDeclaration)!); return isClassDerivedFromDeclaringClasses(enclosingClass, prop) ? enclosingClass : undefined; }); // A protected property is accessible if the property is within the declaring class or classes derived from it if (!enclosingClass) { // allow PropertyAccessibility if context is in function with this parameter // static member access is disallow let thisParameter: ParameterDeclaration | undefined; if (flags & ModifierFlags.Static || !(thisParameter = getThisParameterFromNodeContext(node)) || !thisParameter.type) { error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_within_class_1_and_its_subclasses, symbolToString(prop), typeToString(getDeclaringClass(prop) || type)); return false; } const thisType = getTypeFromTypeNode(thisParameter.type); enclosingClass = (((thisType.flags & TypeFlags.TypeParameter) ? getConstraintOfTypeParameter(thisType) : thisType) as TypeReference).target; } // No further restrictions for static properties if (flags & ModifierFlags.Static) { return true; } if (type.flags & TypeFlags.TypeParameter) { // get the original type -- represented as the type constraint of the 'this' type type = (type as TypeParameter).isThisType ? getConstraintOfTypeParameter(type)! : getBaseConstraintOfType(type)!; // TODO: GH#18217 Use a different variable that's allowed to be undefined } if (!type || !hasBaseType(type, enclosingClass)) { error(errorNode, Diagnostics.Property_0_is_protected_and_only_accessible_through_an_instance_of_class_1, symbolToString(prop), typeToString(enclosingClass)); return false; } return true; } function getThisParameterFromNodeContext(node: Node) { const thisContainer = getThisContainer(node, /* includeArrowFunctions */ false); return thisContainer && isFunctionLike(thisContainer) ? getThisParameter(thisContainer) : undefined; } function symbolHasNonMethodDeclaration(symbol: Symbol) { return !!forEachProperty(symbol, prop => !(prop.flags & SymbolFlags.Method)); } function checkNonNullExpression(node: Expression | QualifiedName) { return checkNonNullType(checkExpression(node), node); } function isNullableType(type: Type) { return !!((strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable); } function getNonNullableTypeIfNeeded(type: Type) { return isNullableType(type) ? getNonNullableType(type) : type; } function reportObjectPossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) { error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ? Diagnostics.Object_is_possibly_null_or_undefined : Diagnostics.Object_is_possibly_undefined : Diagnostics.Object_is_possibly_null ); } function reportCannotInvokePossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) { error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ? Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined : Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined : Diagnostics.Cannot_invoke_an_object_which_is_possibly_null ); } function checkNonNullTypeWithReporter( type: Type, node: Node, reportError: (node: Node, kind: TypeFlags) => void ): Type { if (strictNullChecks && type.flags & TypeFlags.Unknown) { error(node, Diagnostics.Object_is_of_type_unknown); return errorType; } const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable; if (kind) { reportError(node, kind); const t = getNonNullableType(type); return t.flags & (TypeFlags.Nullable | TypeFlags.Never) ? errorType : t; } return type; } function checkNonNullType(type: Type, node: Node) { return checkNonNullTypeWithReporter(type, node, reportObjectPossiblyNullOrUndefinedError); } function checkNonNullNonVoidType(type: Type, node: Node): Type { const nonNullType = checkNonNullType(type, node); if (nonNullType !== errorType && nonNullType.flags & TypeFlags.Void) { error(node, Diagnostics.Object_is_possibly_undefined); } return nonNullType; } function checkPropertyAccessExpression(node: PropertyAccessExpression) { return node.flags & NodeFlags.OptionalChain ? checkPropertyAccessChain(node as PropertyAccessChain) : checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name); } function checkPropertyAccessChain(node: PropertyAccessChain) { const leftType = checkExpression(node.expression); const nonOptionalType = getOptionalExpressionType(leftType, node.expression); return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name), node, nonOptionalType !== leftType); } function checkQualifiedName(node: QualifiedName) { return checkPropertyAccessExpressionOrQualifiedName(node, node.left, checkNonNullExpression(node.left), node.right); } function isMethodAccessForCall(node: Node) { while (node.parent.kind === SyntaxKind.ParenthesizedExpression) { node = node.parent; } return isCallOrNewExpression(node.parent) && node.parent.expression === node; } // Lookup the private identifier lexically. function lookupSymbolForPrivateIdentifierDeclaration(propName: __String, location: Node): Symbol | undefined { for (let containingClass = getContainingClass(location); !!containingClass; containingClass = getContainingClass(containingClass)) { const { symbol } = containingClass; const name = getSymbolNameForPrivateIdentifier(symbol, propName); const prop = (symbol.members && symbol.members.get(name)) || (symbol.exports && symbol.exports.get(name)); if (prop) { return prop; } } } function getPrivateIdentifierPropertyOfType(leftType: Type, lexicallyScopedIdentifier: Symbol): Symbol | undefined { return getPropertyOfType(leftType, lexicallyScopedIdentifier.escapedName); } function checkPrivateIdentifierPropertyAccess(leftType: Type, right: PrivateIdentifier, lexicallyScopedIdentifier: Symbol | undefined): boolean { // Either the identifier could not be looked up in the lexical scope OR the lexically scoped identifier did not exist on the type. // Find a private identifier with the same description on the type. let propertyOnType: Symbol | undefined; const properties = getPropertiesOfType(leftType); if (properties) { forEach(properties, (symbol: Symbol) => { const decl = symbol.valueDeclaration; if (decl && isNamedDeclaration(decl) && isPrivateIdentifier(decl.name) && decl.name.escapedText === right.escapedText) { propertyOnType = symbol; return true; } }); } const diagName = diagnosticName(right); if (propertyOnType) { const typeValueDecl = propertyOnType.valueDeclaration; const typeClass = getContainingClass(typeValueDecl); Debug.assert(!!typeClass); // We found a private identifier property with the same description. // Either: // - There is a lexically scoped private identifier AND it shadows the one we found on the type. // - It is an attempt to access the private identifier outside of the class. if (lexicallyScopedIdentifier) { const lexicalValueDecl = lexicallyScopedIdentifier.valueDeclaration; const lexicalClass = getContainingClass(lexicalValueDecl); Debug.assert(!!lexicalClass); if (findAncestor(lexicalClass, n => typeClass === n)) { const diagnostic = error( right, Diagnostics.The_property_0_cannot_be_accessed_on_type_1_within_this_class_because_it_is_shadowed_by_another_private_identifier_with_the_same_spelling, diagName, typeToString(leftType) ); addRelatedInfo( diagnostic, createDiagnosticForNode( lexicalValueDecl, Diagnostics.The_shadowing_declaration_of_0_is_defined_here, diagName ), createDiagnosticForNode( typeValueDecl, Diagnostics.The_declaration_of_0_that_you_probably_intended_to_use_is_defined_here, diagName ) ); return true; } } error( right, Diagnostics.Property_0_is_not_accessible_outside_class_1_because_it_has_a_private_identifier, diagName, diagnosticName(typeClass.name || anon) ); return true; } return false; } function isThisPropertyAccessInConstructor(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) { return (isConstructorDeclaredProperty(prop) || isThisProperty(node) && isAutoTypedProperty(prop)) && getThisContainer(node, /*includeArrowFunctions*/ true) === getDeclaringConstructor(prop); } function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier) { const parentSymbol = getNodeLinks(left).resolvedSymbol; const assignmentKind = getAssignmentTargetKind(node); const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType); if (isPrivateIdentifier(right)) { checkExternalEmitHelpers(node, ExternalEmitHelpers.ClassPrivateFieldGet); } const isAnyLike = isTypeAny(apparentType) || apparentType === silentNeverType; let prop: Symbol | undefined; if (isPrivateIdentifier(right)) { const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(right.escapedText, right); if (isAnyLike) { if (lexicallyScopedSymbol) { return apparentType; } if (!getContainingClass(right)) { grammarErrorOnNode(right, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); return anyType; } } prop = lexicallyScopedSymbol ? getPrivateIdentifierPropertyOfType(leftType, lexicallyScopedSymbol) : undefined; // Check for private-identifier-specific shadowing and lexical-scoping errors. if (!prop && checkPrivateIdentifierPropertyAccess(leftType, right, lexicallyScopedSymbol)) { return errorType; } } else { if (isAnyLike) { if (isIdentifier(left) && parentSymbol) { markAliasReferenced(parentSymbol, node); } return apparentType; } prop = getPropertyOfType(apparentType, right.escapedText); } if (isIdentifier(left) && parentSymbol && !(prop && isConstEnumOrConstEnumOnlyModule(prop))) { markAliasReferenced(parentSymbol, node); } let propType: Type; if (!prop) { const indexInfo = !isPrivateIdentifier(right) && (assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) || isThisTypeParameter(leftType)) ? getIndexInfoOfType(apparentType, IndexKind.String) : undefined; if (!(indexInfo && indexInfo.type)) { if (isJSLiteralType(leftType)) { return anyType; } if (leftType.symbol === globalThisSymbol) { if (globalThisSymbol.exports!.has(right.escapedText) && (globalThisSymbol.exports!.get(right.escapedText)!.flags & SymbolFlags.BlockScoped)) { error(right, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(right.escapedText), typeToString(leftType)); } else if (noImplicitAny) { error(right, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(leftType)); } return anyType; } if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) { reportNonexistentProperty(right, isThisTypeParameter(leftType) ? apparentType : leftType); } return errorType; } if (indexInfo.isReadonly && (isAssignmentTarget(node) || isDeleteTarget(node))) { error(node, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(apparentType)); } propType = (compilerOptions.noUncheckedIndexedAccess && !isAssignmentTarget(node)) ? getUnionType([indexInfo.type, undefinedType]) : indexInfo.type; } else { if (getDeclarationNodeFlagsFromSymbol(prop) & NodeFlags.Deprecated && isUncalledFunctionReference(node, prop)) { errorOrSuggestion(/* isError */ false, right, Diagnostics._0_is_deprecated, right.escapedText as string); } checkPropertyNotUsedBeforeDeclaration(prop, node, right); markPropertyAsReferenced(prop, node, left.kind === SyntaxKind.ThisKeyword); getNodeLinks(node).resolvedSymbol = prop; checkPropertyAccessibility(node, left.kind === SyntaxKind.SuperKeyword, apparentType, prop); if (isAssignmentToReadonlyEntity(node as Expression, prop, assignmentKind)) { error(right, Diagnostics.Cannot_assign_to_0_because_it_is_a_read_only_property, idText(right)); return errorType; } propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : getConstraintForLocation(getTypeOfSymbol(prop), node); } return getFlowTypeOfAccessExpression(node, prop, propType, right); } function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node) { // Only compute control flow type if this is a property access expression that isn't an // assignment target, and the referenced property was declared as a variable, property, // accessor, or optional method. const assignmentKind = getAssignmentTargetKind(node); if (!isAccessExpression(node) || assignmentKind === AssignmentKind.Definite || prop && !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property | SymbolFlags.Accessor)) && !(prop.flags & SymbolFlags.Method && propType.flags & TypeFlags.Union)) { return propType; } if (propType === autoType) { return getFlowTypeOfProperty(node, prop); } // If strict null checks and strict property initialization checks are enabled, if we have // a this.xxx property access, if the property is an instance property without an initializer, // and if we are in a constructor of the same class as the property declaration, assume that // the property is uninitialized at the top of the control flow. let assumeUninitialized = false; if (strictNullChecks && strictPropertyInitialization && node.expression.kind === SyntaxKind.ThisKeyword) { const declaration = prop && prop.valueDeclaration; if (declaration && isInstancePropertyWithoutInitializer(declaration)) { const flowContainer = getControlFlowContainer(node); if (flowContainer.kind === SyntaxKind.Constructor && flowContainer.parent === declaration.parent && !(declaration.flags & NodeFlags.Ambient)) { assumeUninitialized = true; } } } else if (strictNullChecks && prop && prop.valueDeclaration && isPropertyAccessExpression(prop.valueDeclaration) && getAssignmentDeclarationPropertyAccessKind(prop.valueDeclaration) && getControlFlowContainer(node) === getControlFlowContainer(prop.valueDeclaration)) { assumeUninitialized = true; } const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType); if (assumeUninitialized && !(getFalsyFlags(propType) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) { error(errorNode, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217 // Return the declared type to reduce follow-on errors return propType; } return assignmentKind ? getBaseTypeOfLiteralType(flowType) : flowType; } function checkPropertyNotUsedBeforeDeclaration(prop: Symbol, node: PropertyAccessExpression | QualifiedName, right: Identifier | PrivateIdentifier): void { const { valueDeclaration } = prop; if (!valueDeclaration || getSourceFileOfNode(node).isDeclarationFile) { return; } let diagnosticMessage; const declarationName = idText(right); if (isInPropertyInitializer(node) && !(isAccessExpression(node) && isAccessExpression(node.expression)) && !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right) && !isPropertyDeclaredInAncestorClass(prop)) { diagnosticMessage = error(right, Diagnostics.Property_0_is_used_before_its_initialization, declarationName); } else if (valueDeclaration.kind === SyntaxKind.ClassDeclaration && node.parent.kind !== SyntaxKind.TypeReference && !(valueDeclaration.flags & NodeFlags.Ambient) && !isBlockScopedNameDeclaredBeforeUse(valueDeclaration, right)) { diagnosticMessage = error(right, Diagnostics.Class_0_used_before_its_declaration, declarationName); } if (diagnosticMessage) { addRelatedInfo(diagnosticMessage, createDiagnosticForNode(valueDeclaration, Diagnostics._0_is_declared_here, declarationName) ); } } function isInPropertyInitializer(node: Node): boolean { return !!findAncestor(node, node => { switch (node.kind) { case SyntaxKind.PropertyDeclaration: return true; case SyntaxKind.PropertyAssignment: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.SpreadAssignment: case SyntaxKind.ComputedPropertyName: case SyntaxKind.TemplateSpan: case SyntaxKind.JsxExpression: case SyntaxKind.JsxAttribute: case SyntaxKind.JsxAttributes: case SyntaxKind.JsxSpreadAttribute: case SyntaxKind.JsxOpeningElement: case SyntaxKind.ExpressionWithTypeArguments: case SyntaxKind.HeritageClause: return false; default: return isExpressionNode(node) ? false : "quit"; } }); } /** * It's possible that "prop.valueDeclaration" is a local declaration, but the property was also declared in a superclass. * In that case we won't consider it used before its declaration, because it gets its value from the superclass' declaration. */ function isPropertyDeclaredInAncestorClass(prop: Symbol): boolean { if (!(prop.parent!.flags & SymbolFlags.Class)) { return false; } let classType: InterfaceType | undefined = getTypeOfSymbol(prop.parent!) as InterfaceType; while (true) { classType = classType.symbol && getSuperClass(classType) as InterfaceType | undefined; if (!classType) { return false; } const superProperty = getPropertyOfType(classType, prop.escapedName); if (superProperty && superProperty.valueDeclaration) { return true; } } } function getSuperClass(classType: InterfaceType): Type | undefined { const x = getBaseTypes(classType); if (x.length === 0) { return undefined; } return getIntersectionType(x); } function reportNonexistentProperty(propNode: Identifier | PrivateIdentifier, containingType: Type) { let errorInfo: DiagnosticMessageChain | undefined; let relatedInfo: Diagnostic | undefined; if (!isPrivateIdentifier(propNode) && containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) { for (const subtype of (containingType as UnionType).types) { if (!getPropertyOfType(subtype, propNode.escapedText) && !getIndexInfoOfType(subtype, IndexKind.String)) { errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(subtype)); break; } } } if (typeHasStaticProperty(propNode.escapedText, containingType)) { errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_is_a_static_member_of_type_1, declarationNameToString(propNode), typeToString(containingType)); } else { const promisedType = getPromisedTypeOfPromise(containingType); if (promisedType && getPropertyOfType(promisedType, propNode.escapedText)) { errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType)); relatedInfo = createDiagnosticForNode(propNode, Diagnostics.Did_you_forget_to_use_await); } else { const missingProperty = declarationNameToString(propNode); const container = typeToString(containingType); const libSuggestion = getSuggestedLibForNonExistentProperty(missingProperty, containingType); if (libSuggestion !== undefined) { errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_2_or_later, missingProperty, container, libSuggestion); } else { const suggestion = getSuggestedSymbolForNonexistentProperty(propNode, containingType); if (suggestion !== undefined) { const suggestedName = symbolName(suggestion); errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, missingProperty, container, suggestedName); relatedInfo = suggestion.valueDeclaration && createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestedName); } else { errorInfo = chainDiagnosticMessages(elaborateNeverIntersection(errorInfo, containingType), Diagnostics.Property_0_does_not_exist_on_type_1, missingProperty, container); } } } } const resultDiagnostic = createDiagnosticForNodeFromMessageChain(propNode, errorInfo); if (relatedInfo) { addRelatedInfo(resultDiagnostic, relatedInfo); } diagnostics.add(resultDiagnostic); } function typeHasStaticProperty(propName: __String, containingType: Type): boolean { const prop = containingType.symbol && getPropertyOfType(getTypeOfSymbol(containingType.symbol), propName); return prop !== undefined && prop.valueDeclaration && hasSyntacticModifier(prop.valueDeclaration, ModifierFlags.Static); } function getSuggestedLibForNonExistentName(name: __String | Identifier) { const missingName = diagnosticName(name); const allFeatures = getScriptTargetFeatures(); const libTargets = getOwnKeys(allFeatures); for (const libTarget of libTargets) { const containingTypes = getOwnKeys(allFeatures[libTarget]); if (containingTypes !== undefined && contains(containingTypes, missingName)) { return libTarget; } } } function getSuggestedLibForNonExistentProperty(missingProperty: string, containingType: Type) { const container = getApparentType(containingType).symbol; if (!container) { return undefined; } const allFeatures = getScriptTargetFeatures(); const libTargets = getOwnKeys(allFeatures); for (const libTarget of libTargets) { const featuresOfLib = allFeatures[libTarget]; const featuresOfContainingType = featuresOfLib[symbolName(container)]; if (featuresOfContainingType !== undefined && contains(featuresOfContainingType, missingProperty)) { return libTarget; } } } function getSuggestedSymbolForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined { return getSpellingSuggestionForName(isString(name) ? name : idText(name), getPropertiesOfType(containingType), SymbolFlags.Value); } function getSuggestedSymbolForNonexistentJSXAttribute(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined { const strName = isString(name) ? name : idText(name); const properties = getPropertiesOfType(containingType); const jsxSpecific = strName === "for" ? find(properties, x => symbolName(x) === "htmlFor") : strName === "class" ? find(properties, x => symbolName(x) === "className") : undefined; return jsxSpecific ?? getSpellingSuggestionForName(strName, properties, SymbolFlags.Value); } function getSuggestionForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): string | undefined { const suggestion = getSuggestedSymbolForNonexistentProperty(name, containingType); return suggestion && symbolName(suggestion); } function getSuggestedSymbolForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): Symbol | undefined { Debug.assert(outerName !== undefined, "outername should always be defined"); const result = resolveNameHelper(location, outerName, meaning, /*nameNotFoundMessage*/ undefined, outerName, /*isUse*/ false, /*excludeGlobals*/ false, (symbols, name, meaning) => { Debug.assertEqual(outerName, name, "name should equal outerName"); const symbol = getSymbol(symbols, name, meaning); // Sometimes the symbol is found when location is a return type of a function: `typeof x` and `x` is declared in the body of the function // So the table *contains* `x` but `x` isn't actually in scope. // However, resolveNameHelper will continue and call this callback again, so we'll eventually get a correct suggestion. return symbol || getSpellingSuggestionForName(unescapeLeadingUnderscores(name), arrayFrom(symbols.values()), meaning); }); return result; } function getSuggestionForNonexistentSymbol(location: Node | undefined, outerName: __String, meaning: SymbolFlags): string | undefined { const symbolResult = getSuggestedSymbolForNonexistentSymbol(location, outerName, meaning); return symbolResult && symbolName(symbolResult); } function getSuggestedSymbolForNonexistentModule(name: Identifier, targetModule: Symbol): Symbol | undefined { return targetModule.exports && getSpellingSuggestionForName(idText(name), getExportsOfModuleAsArray(targetModule), SymbolFlags.ModuleMember); } function getSuggestionForNonexistentExport(name: Identifier, targetModule: Symbol): string | undefined { const suggestion = getSuggestedSymbolForNonexistentModule(name, targetModule); return suggestion && symbolName(suggestion); } function getSuggestionForNonexistentIndexSignature(objectType: Type, expr: ElementAccessExpression, keyedType: Type): string | undefined { // check if object type has setter or getter function hasProp(name: "set" | "get") { const prop = getPropertyOfObjectType(objectType, <__String>name); if (prop) { const s = getSingleCallSignature(getTypeOfSymbol(prop)); return !!s && getMinArgumentCount(s) >= 1 && isTypeAssignableTo(keyedType, getTypeAtPosition(s, 0)); } return false; }; const suggestedMethod = isAssignmentTarget(expr) ? "set" : "get"; if (!hasProp(suggestedMethod)) { return undefined; } let suggestion = tryGetPropertyAccessOrIdentifierToString(expr.expression); if (suggestion === undefined) { suggestion = suggestedMethod; } else { suggestion += "." + suggestedMethod; } return suggestion; } /** * Given a name and a list of symbols whose names are *not* equal to the name, return a spelling suggestion if there is one that is close enough. * Names less than length 3 only check for case-insensitive equality, not levenshtein distance. * * If there is a candidate that's the same except for case, return that. * If there is a candidate that's within one edit of the name, return that. * Otherwise, return the candidate with the smallest Levenshtein distance, * except for candidates: * * With no name * * Whose meaning doesn't match the `meaning` parameter. * * Whose length differs from the target name by more than 0.34 of the length of the name. * * Whose levenshtein distance is more than 0.4 of the length of the name * (0.4 allows 1 substitution/transposition for every 5 characters, * and 1 insertion/deletion at 3 characters) */ function getSpellingSuggestionForName(name: string, symbols: Symbol[], meaning: SymbolFlags): Symbol | undefined { return getSpellingSuggestion(name, symbols, getCandidateName); function getCandidateName(candidate: Symbol) { const candidateName = symbolName(candidate); if (startsWith(candidateName, "\"")) { return undefined; } if (candidate.flags & meaning) { return candidateName; } if (candidate.flags & SymbolFlags.Alias) { const alias = tryResolveAlias(candidate); if (alias && alias.flags & meaning) { return candidateName; } } return undefined; } } function markPropertyAsReferenced(prop: Symbol, nodeForCheckWriteOnly: Node | undefined, isThisAccess: boolean) { const valueDeclaration = prop && (prop.flags & SymbolFlags.ClassMember) && prop.valueDeclaration; if (!valueDeclaration) { return; } const hasPrivateModifier = hasEffectiveModifier(valueDeclaration, ModifierFlags.Private); const hasPrivateIdentifier = isNamedDeclaration(prop.valueDeclaration) && isPrivateIdentifier(prop.valueDeclaration.name); if (!hasPrivateModifier && !hasPrivateIdentifier) { return; } if (nodeForCheckWriteOnly && isWriteOnlyAccess(nodeForCheckWriteOnly) && !(prop.flags & SymbolFlags.SetAccessor)) { return; } if (isThisAccess) { // Find any FunctionLikeDeclaration because those create a new 'this' binding. But this should only matter for methods (or getters/setters). const containingMethod = findAncestor(nodeForCheckWriteOnly, isFunctionLikeDeclaration); if (containingMethod && containingMethod.symbol === prop) { return; } } (getCheckFlags(prop) & CheckFlags.Instantiated ? getSymbolLinks(prop).target : prop)!.isReferenced = SymbolFlags.All; } function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName | ImportTypeNode, propertyName: __String): boolean { switch (node.kind) { case SyntaxKind.PropertyAccessExpression: return isValidPropertyAccessWithType(node, node.expression.kind === SyntaxKind.SuperKeyword, propertyName, getWidenedType(checkExpression(node.expression))); case SyntaxKind.QualifiedName: return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getWidenedType(checkExpression(node.left))); case SyntaxKind.ImportType: return isValidPropertyAccessWithType(node, /*isSuper*/ false, propertyName, getTypeFromTypeNode(node)); } } function isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: Type, property: Symbol): boolean { return isValidPropertyAccessWithType(node, node.kind === SyntaxKind.PropertyAccessExpression && node.expression.kind === SyntaxKind.SuperKeyword, property.escapedName, type); // Previously we validated the 'this' type of methods but this adversely affected performance. See #31377 for more context. } function isValidPropertyAccessWithType( node: PropertyAccessExpression | QualifiedName | ImportTypeNode, isSuper: boolean, propertyName: __String, type: Type): boolean { if (type === errorType || isTypeAny(type)) { return true; } const prop = getPropertyOfType(type, propertyName); if (prop) { if (isPropertyAccessExpression(node) && prop.valueDeclaration && isPrivateIdentifierPropertyDeclaration(prop.valueDeclaration)) { const declClass = getContainingClass(prop.valueDeclaration); return !isOptionalChain(node) && !!findAncestor(node, parent => parent === declClass); } return checkPropertyAccessibility(node, isSuper, type, prop); } // In js files properties of unions are allowed in completion return isInJSFile(node) && (type.flags & TypeFlags.Union) !== 0 && (type).types.some(elementType => isValidPropertyAccessWithType(node, isSuper, propertyName, elementType)); } /** * Return the symbol of the for-in variable declared or referenced by the given for-in statement. */ function getForInVariableSymbol(node: ForInStatement): Symbol | undefined { const initializer = node.initializer; if (initializer.kind === SyntaxKind.VariableDeclarationList) { const variable = (initializer).declarations[0]; if (variable && !isBindingPattern(variable.name)) { return getSymbolOfNode(variable); } } else if (initializer.kind === SyntaxKind.Identifier) { return getResolvedSymbol(initializer); } return undefined; } /** * Return true if the given type is considered to have numeric property names. */ function hasNumericPropertyNames(type: Type) { return getIndexTypeOfType(type, IndexKind.Number) && !getIndexTypeOfType(type, IndexKind.String); } /** * Return true if given node is an expression consisting of an identifier (possibly parenthesized) * that references a for-in variable for an object with numeric property names. */ function isForInVariableForNumericPropertyNames(expr: Expression) { const e = skipParentheses(expr); if (e.kind === SyntaxKind.Identifier) { const symbol = getResolvedSymbol(e); if (symbol.flags & SymbolFlags.Variable) { let child: Node = expr; let node = expr.parent; while (node) { if (node.kind === SyntaxKind.ForInStatement && child === (node).statement && getForInVariableSymbol(node) === symbol && hasNumericPropertyNames(getTypeOfExpression((node).expression))) { return true; } child = node; node = node.parent; } } } return false; } function checkIndexedAccess(node: ElementAccessExpression): Type { return node.flags & NodeFlags.OptionalChain ? checkElementAccessChain(node as ElementAccessChain) : checkElementAccessExpression(node, checkNonNullExpression(node.expression)); } function checkElementAccessChain(node: ElementAccessChain) { const exprType = checkExpression(node.expression); const nonOptionalType = getOptionalExpressionType(exprType, node.expression); return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression)), node, nonOptionalType !== exprType); } function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type): Type { const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType; const indexExpression = node.argumentExpression; const indexType = checkExpression(indexExpression); if (objectType === errorType || objectType === silentNeverType) { return objectType; } if (isConstEnumObjectType(objectType) && !isStringLiteralLike(indexExpression)) { error(indexExpression, Diagnostics.A_const_enum_member_can_only_be_accessed_using_a_string_literal); return errorType; } const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType; const accessFlags = isAssignmentTarget(node) ? AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) : AccessFlags.None; const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, /*noUncheckedIndexedAccessCandidate*/ undefined, node, accessFlags | AccessFlags.ExpressionPosition) || errorType; return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node); } function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean { if (expressionType === errorType) { // There is already an error, so no need to report one. return false; } if (!isWellKnownSymbolSyntactically(expression)) { return false; } // Make sure the property type is the primitive symbol type if ((expressionType.flags & TypeFlags.ESSymbolLike) === 0) { if (reportError) { error(expression, Diagnostics.A_computed_property_name_of_the_form_0_must_be_of_type_symbol, getTextOfNode(expression)); } return false; } // The name is Symbol., so make sure Symbol actually resolves to the // global Symbol object const leftHandSide = (expression).expression; const leftHandSideSymbol = getResolvedSymbol(leftHandSide); if (!leftHandSideSymbol) { return false; } const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ true); if (!globalESSymbol) { // Already errored when we tried to look up the symbol return false; } if (leftHandSideSymbol !== globalESSymbol) { if (reportError) { error(leftHandSide, Diagnostics.Symbol_reference_does_not_refer_to_the_global_Symbol_constructor_object); } return false; } return true; } function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement { return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node); } function resolveUntypedCall(node: CallLikeExpression): Signature { if (callLikeExpressionMayHaveTypeArguments(node)) { // Check type arguments even though we will give an error that untyped calls may not accept type arguments. // This gets us diagnostics for the type arguments and marks them as referenced. forEach(node.typeArguments, checkSourceElement); } if (node.kind === SyntaxKind.TaggedTemplateExpression) { checkExpression(node.template); } else if (isJsxOpeningLikeElement(node)) { checkExpression(node.attributes); } else if (node.kind !== SyntaxKind.Decorator) { forEach((node).arguments, argument => { checkExpression(argument); }); } return anySignature; } function resolveErrorCall(node: CallLikeExpression): Signature { resolveUntypedCall(node); return unknownSignature; } // Re-order candidate signatures into the result array. Assumes the result array to be empty. // The candidate list orders groups in reverse, but within a group signatures are kept in declaration order // A nit here is that we reorder only signatures that belong to the same symbol, // so order how inherited signatures are processed is still preserved. // interface A { (x: string): void } // interface B extends A { (x: 'foo'): string } // const b: B; // b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void] function reorderCandidates(signatures: readonly Signature[], result: Signature[], callChainFlags: SignatureFlags): void { let lastParent: Node | undefined; let lastSymbol: Symbol | undefined; let cutoffIndex = 0; let index: number | undefined; let specializedIndex = -1; let spliceIndex: number; Debug.assert(!result.length); for (const signature of signatures) { const symbol = signature.declaration && getSymbolOfNode(signature.declaration); const parent = signature.declaration && signature.declaration.parent; if (!lastSymbol || symbol === lastSymbol) { if (lastParent && parent === lastParent) { index = index! + 1; } else { lastParent = parent; index = cutoffIndex; } } else { // current declaration belongs to a different symbol // set cutoffIndex so re-orderings in the future won't change result set from 0 to cutoffIndex index = cutoffIndex = result.length; lastParent = parent; } lastSymbol = symbol; // specialized signatures always need to be placed before non-specialized signatures regardless // of the cutoff position; see GH#1133 if (signatureHasLiteralTypes(signature)) { specializedIndex++; spliceIndex = specializedIndex; // The cutoff index always needs to be greater than or equal to the specialized signature index // in order to prevent non-specialized signatures from being added before a specialized // signature. cutoffIndex++; } else { spliceIndex = index; } result.splice(spliceIndex, 0, callChainFlags ? getOptionalCallSignature(signature, callChainFlags) : signature); } } function isSpreadArgument(arg: Expression | undefined): arg is Expression { return !!arg && (arg.kind === SyntaxKind.SpreadElement || arg.kind === SyntaxKind.SyntheticExpression && (arg).isSpread); } function getSpreadArgumentIndex(args: readonly Expression[]): number { return findIndex(args, isSpreadArgument); } function acceptsVoid(t: Type): boolean { return !!(t.flags & TypeFlags.Void); } function acceptsVoidUndefinedUnknownOrAny(t: Type): boolean { return !!(t.flags & (TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Unknown | TypeFlags.Any)); } function hasCorrectArity(node: CallLikeExpression, args: readonly Expression[], signature: Signature, signatureHelpTrailingComma = false) { let argCount: number; let callIsIncomplete = false; // In incomplete call we want to be lenient when we have too few arguments let effectiveParameterCount = getParameterCount(signature); let effectiveMinimumArguments = getMinArgumentCount(signature); if (node.kind === SyntaxKind.TaggedTemplateExpression) { argCount = args.length; if (node.template.kind === SyntaxKind.TemplateExpression) { // If a tagged template expression lacks a tail literal, the call is incomplete. // Specifically, a template only can end in a TemplateTail or a Missing literal. const lastSpan = last(node.template.templateSpans); // we should always have at least one span. callIsIncomplete = nodeIsMissing(lastSpan.literal) || !!lastSpan.literal.isUnterminated; } else { // If the template didn't end in a backtick, or its beginning occurred right prior to EOF, // then this might actually turn out to be a TemplateHead in the future; // so we consider the call to be incomplete. const templateLiteral = node.template; Debug.assert(templateLiteral.kind === SyntaxKind.NoSubstitutionTemplateLiteral); callIsIncomplete = !!templateLiteral.isUnterminated; } } else if (node.kind === SyntaxKind.Decorator) { argCount = getDecoratorArgumentCount(node, signature); } else if (isJsxOpeningLikeElement(node)) { callIsIncomplete = node.attributes.end === node.end; if (callIsIncomplete) { return true; } argCount = effectiveMinimumArguments === 0 ? args.length : 1; effectiveParameterCount = args.length === 0 ? effectiveParameterCount : 1; // class may have argumentless ctor functions - still resolve ctor and compare vs props member type effectiveMinimumArguments = Math.min(effectiveMinimumArguments, 1); // sfc may specify context argument - handled by framework and not typechecked } else if (!node.arguments) { // This only happens when we have something of the form: 'new C' Debug.assert(node.kind === SyntaxKind.NewExpression); return getMinArgumentCount(signature) === 0; } else { argCount = signatureHelpTrailingComma ? args.length + 1 : args.length; // If we are missing the close parenthesis, the call is incomplete. callIsIncomplete = node.arguments.end === node.end; // If a spread argument is present, check that it corresponds to a rest parameter or at least that it's in the valid range. const spreadArgIndex = getSpreadArgumentIndex(args); if (spreadArgIndex >= 0) { return spreadArgIndex >= getMinArgumentCount(signature) && (hasEffectiveRestParameter(signature) || spreadArgIndex < getParameterCount(signature)); } } // Too many arguments implies incorrect arity. if (!hasEffectiveRestParameter(signature) && argCount > effectiveParameterCount) { return false; } // If the call is incomplete, we should skip the lower bound check. // JSX signatures can have extra parameters provided by the library which we don't check if (callIsIncomplete || argCount >= effectiveMinimumArguments) { return true; } for (let i = argCount; i < effectiveMinimumArguments; i++) { const type = getTypeAtPosition(signature, i); if (filterType(type, isInJSFile(node) && !strictNullChecks ? acceptsVoidUndefinedUnknownOrAny : acceptsVoid).flags & TypeFlags.Never) { return false; } } return true; } function hasCorrectTypeArgumentArity(signature: Signature, typeArguments: NodeArray | undefined) { // If the user supplied type arguments, but the number of type arguments does not match // the declared number of type parameters, the call has an incorrect arity. const numTypeParameters = length(signature.typeParameters); const minTypeArgumentCount = getMinTypeArgumentCount(signature.typeParameters); return !some(typeArguments) || (typeArguments.length >= minTypeArgumentCount && typeArguments.length <= numTypeParameters); } // If type has a single call signature and no other members, return that signature. Otherwise, return undefined. function getSingleCallSignature(type: Type): Signature | undefined { return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false); } function getSingleCallOrConstructSignature(type: Type): Signature | undefined { return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false) || getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ false); } function getSingleSignature(type: Type, kind: SignatureKind, allowMembers: boolean): Signature | undefined { if (type.flags & TypeFlags.Object) { const resolved = resolveStructuredTypeMembers(type); if (allowMembers || resolved.properties.length === 0 && !resolved.stringIndexInfo && !resolved.numberIndexInfo) { if (kind === SignatureKind.Call && resolved.callSignatures.length === 1 && resolved.constructSignatures.length === 0) { return resolved.callSignatures[0]; } if (kind === SignatureKind.Construct && resolved.constructSignatures.length === 1 && resolved.callSignatures.length === 0) { return resolved.constructSignatures[0]; } } } return undefined; } // Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec) function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, inferenceContext?: InferenceContext, compareTypes?: TypeComparer): Signature { const context = createInferenceContext(signature.typeParameters!, signature, InferenceFlags.None, compareTypes); // We clone the inferenceContext to avoid fixing. For example, when the source signature is (x: T) => T[] and // the contextual signature is (...args: A) => B, we want to infer the element type of A's constraint (say 'any') // for T but leave it possible to later infer '[any]' back to A. const restType = getEffectiveRestType(contextualSignature); const mapper = inferenceContext && (restType && restType.flags & TypeFlags.TypeParameter ? inferenceContext.nonFixingMapper : inferenceContext.mapper); const sourceSignature = mapper ? instantiateSignature(contextualSignature, mapper) : contextualSignature; applyToParameterTypes(sourceSignature, signature, (source, target) => { // Type parameters from outer context referenced by source type are fixed by instantiation of the source type inferTypes(context.inferences, source, target); }); if (!inferenceContext) { applyToReturnTypes(contextualSignature, signature, (source, target) => { inferTypes(context.inferences, source, target, InferencePriority.ReturnType); }); } return getSignatureInstantiation(signature, getInferredTypes(context), isInJSFile(contextualSignature.declaration)); } function inferJsxTypeArguments(node: JsxOpeningLikeElement, signature: Signature, checkMode: CheckMode, context: InferenceContext): Type[] { const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node); const checkAttrType = checkExpressionWithContextualType(node.attributes, paramType, context, checkMode); inferTypes(context.inferences, checkAttrType, paramType); return getInferredTypes(context); } function inferTypeArguments(node: CallLikeExpression, signature: Signature, args: readonly Expression[], checkMode: CheckMode, context: InferenceContext): Type[] { if (isJsxOpeningLikeElement(node)) { return inferJsxTypeArguments(node, signature, checkMode, context); } // If a contextual type is available, infer from that type to the return type of the call expression. For // example, given a 'function wrap(cb: (x: T) => U): (x: T) => U' and a call expression // 'let f: (x: string) => number = wrap(s => s.length)', we infer from the declared type of 'f' to the // return type of 'wrap'. if (node.kind !== SyntaxKind.Decorator) { const contextualType = getContextualType(node, every(signature.typeParameters, p => !!getDefaultFromTypeParameter(p)) ? ContextFlags.SkipBindingPatterns : ContextFlags.None); if (contextualType) { // We clone the inference context to avoid disturbing a resolution in progress for an // outer call expression. Effectively we just want a snapshot of whatever has been // inferred for any outer call expression so far. const outerContext = getInferenceContext(node); const outerMapper = getMapperFromContext(cloneInferenceContext(outerContext, InferenceFlags.NoDefault)); const instantiatedType = instantiateType(contextualType, outerMapper); // If the contextual type is a generic function type with a single call signature, we // instantiate the type with its own type parameters and type arguments. This ensures that // the type parameters are not erased to type any during type inference such that they can // be inferred as actual types from the contextual type. For example: // declare function arrayMap(f: (x: T) => U): (a: T[]) => U[]; // const boxElements: (a: A[]) => { value: A }[] = arrayMap(value => ({ value })); // Above, the type of the 'value' parameter is inferred to be 'A'. const contextualSignature = getSingleCallSignature(instantiatedType); const inferenceSourceType = contextualSignature && contextualSignature.typeParameters ? getOrCreateTypeFromSignature(getSignatureInstantiationWithoutFillingInTypeArguments(contextualSignature, contextualSignature.typeParameters)) : instantiatedType; const inferenceTargetType = getReturnTypeOfSignature(signature); // Inferences made from return types have lower priority than all other inferences. inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType); // Create a type mapper for instantiating generic contextual types using the inferences made // from the return type. We need a separate inference pass here because (a) instantiation of // the source type uses the outer context's return mapper (which excludes inferences made from // outer arguments), and (b) we don't want any further inferences going into this context. const returnContext = createInferenceContext(signature.typeParameters!, signature, context.flags); const returnSourceType = instantiateType(contextualType, outerContext && outerContext.returnMapper); inferTypes(returnContext.inferences, returnSourceType, inferenceTargetType); context.returnMapper = some(returnContext.inferences, hasInferenceCandidates) ? getMapperFromContext(cloneInferredPartOfContext(returnContext)) : undefined; } } const restType = getNonArrayRestType(signature); const argCount = restType ? Math.min(getParameterCount(signature) - 1, args.length) : args.length; if (restType && restType.flags & TypeFlags.TypeParameter) { const info = find(context.inferences, info => info.typeParameter === restType); if (info) { info.impliedArity = findIndex(args, isSpreadArgument, argCount) < 0 ? args.length - argCount : undefined; } } const thisType = getThisTypeOfSignature(signature); if (thisType) { const thisArgumentNode = getThisArgumentOfCall(node); const thisArgumentType = thisArgumentNode ? checkExpression(thisArgumentNode) : voidType; inferTypes(context.inferences, thisArgumentType, thisType); } for (let i = 0; i < argCount; i++) { const arg = args[i]; if (arg.kind !== SyntaxKind.OmittedExpression) { const paramType = getTypeAtPosition(signature, i); const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode); inferTypes(context.inferences, argType, paramType); } } if (restType) { const spreadType = getSpreadArgumentType(args, argCount, args.length, restType, context, checkMode); inferTypes(context.inferences, spreadType, restType); } return getInferredTypes(context); } function getMutableArrayOrTupleType(type: Type) { return type.flags & TypeFlags.Union ? mapType(type, getMutableArrayOrTupleType) : type.flags & TypeFlags.Any || isMutableArrayOrTuple(getBaseConstraintOfType(type) || type) ? type : isTupleType(type) ? createTupleType(getTypeArguments(type), type.target.elementFlags, /*readonly*/ false, type.target.labeledElementDeclarations) : createTupleType([type], [ElementFlags.Variadic]); } function getSpreadArgumentType(args: readonly Expression[], index: number, argCount: number, restType: Type, context: InferenceContext | undefined, checkMode: CheckMode) { if (index >= argCount - 1) { const arg = args[argCount - 1]; if (isSpreadArgument(arg)) { // We are inferring from a spread expression in the last argument position, i.e. both the parameter // and the argument are ...x forms. return getMutableArrayOrTupleType(arg.kind === SyntaxKind.SyntheticExpression ? (arg).type : checkExpressionWithContextualType((arg).expression, restType, context, checkMode)); } } const types = []; const flags = []; const names = []; for (let i = index; i < argCount; i++) { const arg = args[i]; if (isSpreadArgument(arg)) { const spreadType = arg.kind === SyntaxKind.SyntheticExpression ? (arg).type : checkExpression((arg).expression); if (isArrayLikeType(spreadType)) { types.push(spreadType); flags.push(ElementFlags.Variadic); } else { types.push(checkIteratedTypeOrElementType(IterationUse.Spread, spreadType, undefinedType, arg.kind === SyntaxKind.SpreadElement ? (arg).expression : arg)); flags.push(ElementFlags.Rest); } } else { const contextualType = getIndexedAccessType(restType, getLiteralType(i - index)); const argType = checkExpressionWithContextualType(arg, contextualType, context, checkMode); const hasPrimitiveContextualType = maybeTypeOfKind(contextualType, TypeFlags.Primitive | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping); types.push(hasPrimitiveContextualType ? getRegularTypeOfLiteralType(argType) : getWidenedLiteralType(argType)); flags.push(ElementFlags.Required); } if (arg.kind === SyntaxKind.SyntheticExpression && (arg as SyntheticExpression).tupleNameSource) { names.push((arg as SyntheticExpression).tupleNameSource!); } } return createTupleType(types, flags, /*readonly*/ false, length(names) === length(types) ? names : undefined); } function checkTypeArguments(signature: Signature, typeArgumentNodes: readonly TypeNode[], reportErrors: boolean, headMessage?: DiagnosticMessage): Type[] | undefined { const isJavascript = isInJSFile(signature.declaration); const typeParameters = signature.typeParameters!; const typeArgumentTypes = fillMissingTypeArguments(map(typeArgumentNodes, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isJavascript); let mapper: TypeMapper | undefined; for (let i = 0; i < typeArgumentNodes.length; i++) { Debug.assert(typeParameters[i] !== undefined, "Should not call checkTypeArguments with too many type arguments"); const constraint = getConstraintOfTypeParameter(typeParameters[i]); if (constraint) { const errorInfo = reportErrors && headMessage ? (() => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Type_0_does_not_satisfy_the_constraint_1)) : undefined; const typeArgumentHeadMessage = headMessage || Diagnostics.Type_0_does_not_satisfy_the_constraint_1; if (!mapper) { mapper = createTypeMapper(typeParameters, typeArgumentTypes); } const typeArgument = typeArgumentTypes[i]; if (!checkTypeAssignableTo( typeArgument, getTypeWithThisArgument(instantiateType(constraint, mapper), typeArgument), reportErrors ? typeArgumentNodes[i] : undefined, typeArgumentHeadMessage, errorInfo)) { return undefined; } } } return typeArgumentTypes; } function getJsxReferenceKind(node: JsxOpeningLikeElement): JsxReferenceKind { if (isJsxIntrinsicIdentifier(node.tagName)) { return JsxReferenceKind.Mixed; } const tagType = getApparentType(checkExpression(node.tagName)); if (length(getSignaturesOfType(tagType, SignatureKind.Construct))) { return JsxReferenceKind.Component; } if (length(getSignaturesOfType(tagType, SignatureKind.Call))) { return JsxReferenceKind.Function; } return JsxReferenceKind.Mixed; } /** * Check if the given signature can possibly be a signature called by the JSX opening-like element. * @param node a JSX opening-like element we are trying to figure its call signature * @param signature a candidate signature we are trying whether it is a call signature * @param relation a relationship to check parameter and argument type */ function checkApplicableSignatureForJsxOpeningLikeElement( node: JsxOpeningLikeElement, signature: Signature, relation: ESMap, checkMode: CheckMode, reportErrors: boolean, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } ) { // Stateless function components can have maximum of three arguments: "props", "context", and "updater". // However "context" and "updater" are implicit and can't be specify by users. Only the first parameter, props, // can be specified by users through attributes property. const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node); const attributesType = checkExpressionWithContextualType(node.attributes, paramType, /*inferenceContext*/ undefined, checkMode); return checkTagNameDoesNotExpectTooManyArguments() && checkTypeRelatedToAndOptionallyElaborate( attributesType, paramType, relation, reportErrors ? node.tagName : undefined, node.attributes, /*headMessage*/ undefined, containingMessageChain, errorOutputContainer); function checkTagNameDoesNotExpectTooManyArguments(): boolean { const tagType = isJsxOpeningElement(node) || isJsxSelfClosingElement(node) && !isJsxIntrinsicIdentifier(node.tagName) ? checkExpression(node.tagName) : undefined; if (!tagType) { return true; } const tagCallSignatures = getSignaturesOfType(tagType, SignatureKind.Call); if (!length(tagCallSignatures)) { return true; } const factory = getJsxFactoryEntity(node); if (!factory) { return true; } const factorySymbol = resolveEntityName(factory, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, node); if (!factorySymbol) { return true; } const factoryType = getTypeOfSymbol(factorySymbol); const callSignatures = getSignaturesOfType(factoryType, SignatureKind.Call); if (!length(callSignatures)) { return true; } let hasFirstParamSignatures = false; let maxParamCount = 0; // Check that _some_ first parameter expects a FC-like thing, and that some overload of the SFC expects an acceptable number of arguments for (const sig of callSignatures) { const firstparam = getTypeAtPosition(sig, 0); const signaturesOfParam = getSignaturesOfType(firstparam, SignatureKind.Call); if (!length(signaturesOfParam)) continue; for (const paramSig of signaturesOfParam) { hasFirstParamSignatures = true; if (hasEffectiveRestParameter(paramSig)) { return true; // some signature has a rest param, so function components can have an arbitrary number of arguments } const paramCount = getParameterCount(paramSig); if (paramCount > maxParamCount) { maxParamCount = paramCount; } } } if (!hasFirstParamSignatures) { // Not a single signature had a first parameter which expected a signature - for back compat, and // to guard against generic factories which won't have signatures directly, do not error return true; } let absoluteMinArgCount = Infinity; for (const tagSig of tagCallSignatures) { const tagRequiredArgCount = getMinArgumentCount(tagSig); if (tagRequiredArgCount < absoluteMinArgCount) { absoluteMinArgCount = tagRequiredArgCount; } } if (absoluteMinArgCount <= maxParamCount) { return true; // some signature accepts the number of arguments the function component provides } if (reportErrors) { const diag = createDiagnosticForNode(node.tagName, Diagnostics.Tag_0_expects_at_least_1_arguments_but_the_JSX_factory_2_provides_at_most_3, entityNameToString(node.tagName), absoluteMinArgCount, entityNameToString(factory), maxParamCount); const tagNameDeclaration = getSymbolAtLocation(node.tagName)?.valueDeclaration; if (tagNameDeclaration) { addRelatedInfo(diag, createDiagnosticForNode(tagNameDeclaration, Diagnostics._0_is_declared_here, entityNameToString(node.tagName))); } if (errorOutputContainer && errorOutputContainer.skipLogging) { (errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag); } if (!errorOutputContainer.skipLogging) { diagnostics.add(diag); } } return false; } } function getSignatureApplicabilityError( node: CallLikeExpression, args: readonly Expression[], signature: Signature, relation: ESMap, checkMode: CheckMode, reportErrors: boolean, containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, ): readonly Diagnostic[] | undefined { const errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } = { errors: undefined, skipLogging: true }; if (isJsxOpeningLikeElement(node)) { if (!checkApplicableSignatureForJsxOpeningLikeElement(node, signature, relation, checkMode, reportErrors, containingMessageChain, errorOutputContainer)) { Debug.assert(!reportErrors || !!errorOutputContainer.errors, "jsx should have errors when reporting errors"); return errorOutputContainer.errors || emptyArray; } return undefined; } const thisType = getThisTypeOfSignature(signature); if (thisType && thisType !== voidType && node.kind !== SyntaxKind.NewExpression) { // If the called expression is not of the form `x.f` or `x["f"]`, then sourceType = voidType // If the signature's 'this' type is voidType, then the check is skipped -- anything is compatible. // If the expression is a new expression, then the check is skipped. const thisArgumentNode = getThisArgumentOfCall(node); let thisArgumentType: Type; if (thisArgumentNode) { thisArgumentType = checkExpression(thisArgumentNode); if (isOptionalChainRoot(thisArgumentNode.parent)) { thisArgumentType = getNonNullableType(thisArgumentType); } else if (isOptionalChain(thisArgumentNode.parent)) { thisArgumentType = removeOptionalTypeMarker(thisArgumentType); } } else { thisArgumentType = voidType; } const errorNode = reportErrors ? (thisArgumentNode || node) : undefined; const headMessage = Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1; if (!checkTypeRelatedTo(thisArgumentType, thisType, relation, errorNode, headMessage, containingMessageChain, errorOutputContainer)) { Debug.assert(!reportErrors || !!errorOutputContainer.errors, "this parameter should have errors when reporting errors"); return errorOutputContainer.errors || emptyArray; } } const headMessage = Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1; const restType = getNonArrayRestType(signature); const argCount = restType ? Math.min(getParameterCount(signature) - 1, args.length) : args.length; for (let i = 0; i < argCount; i++) { const arg = args[i]; if (arg.kind !== SyntaxKind.OmittedExpression) { const paramType = getTypeAtPosition(signature, i); const argType = checkExpressionWithContextualType(arg, paramType, /*inferenceContext*/ undefined, checkMode); // If one or more arguments are still excluded (as indicated by CheckMode.SkipContextSensitive), // we obtain the regular type of any object literal arguments because we may not have inferred complete // parameter types yet and therefore excess property checks may yield false positives (see #17041). const checkArgType = checkMode & CheckMode.SkipContextSensitive ? getRegularTypeOfObjectLiteral(argType) : argType; if (!checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, reportErrors ? arg : undefined, arg, headMessage, containingMessageChain, errorOutputContainer)) { Debug.assert(!reportErrors || !!errorOutputContainer.errors, "parameter should have errors when reporting errors"); maybeAddMissingAwaitInfo(arg, checkArgType, paramType); return errorOutputContainer.errors || emptyArray; } } } if (restType) { const spreadType = getSpreadArgumentType(args, argCount, args.length, restType, /*context*/ undefined, checkMode); const errorNode = reportErrors ? argCount < args.length ? args[argCount] : node : undefined; if (!checkTypeRelatedTo(spreadType, restType, relation, errorNode, headMessage, /*containingMessageChain*/ undefined, errorOutputContainer)) { Debug.assert(!reportErrors || !!errorOutputContainer.errors, "rest parameter should have errors when reporting errors"); maybeAddMissingAwaitInfo(errorNode, spreadType, restType); return errorOutputContainer.errors || emptyArray; } } return undefined; function maybeAddMissingAwaitInfo(errorNode: Node | undefined, source: Type, target: Type) { if (errorNode && reportErrors && errorOutputContainer.errors && errorOutputContainer.errors.length) { // Bail if target is Promise-like---something else is wrong if (getAwaitedTypeOfPromise(target)) { return; } const awaitedTypeOfSource = getAwaitedTypeOfPromise(source); if (awaitedTypeOfSource && isTypeRelatedTo(awaitedTypeOfSource, target, relation)) { addRelatedInfo(errorOutputContainer.errors[0], createDiagnosticForNode(errorNode, Diagnostics.Did_you_forget_to_use_await)); } } } } /** * Returns the this argument in calls like x.f(...) and x[f](...). Undefined otherwise. */ function getThisArgumentOfCall(node: CallLikeExpression): LeftHandSideExpression | undefined { if (node.kind === SyntaxKind.CallExpression) { const callee = skipOuterExpressions(node.expression); if (isAccessExpression(callee)) { return callee.expression; } } } function createSyntheticExpression(parent: Node, type: Type, isSpread?: boolean, tupleNameSource?: ParameterDeclaration | NamedTupleMember) { const result = parseNodeFactory.createSyntheticExpression(type, isSpread, tupleNameSource); setTextRange(result, parent); setParent(result, parent); return result; } /** * Returns the effective arguments for an expression that works like a function invocation. */ function getEffectiveCallArguments(node: CallLikeExpression): readonly Expression[] { if (node.kind === SyntaxKind.TaggedTemplateExpression) { const template = node.template; const args: Expression[] = [createSyntheticExpression(template, getGlobalTemplateStringsArrayType())]; if (template.kind === SyntaxKind.TemplateExpression) { forEach(template.templateSpans, span => { args.push(span.expression); }); } return args; } if (node.kind === SyntaxKind.Decorator) { return getEffectiveDecoratorArguments(node); } if (isJsxOpeningLikeElement(node)) { return node.attributes.properties.length > 0 || (isJsxOpeningElement(node) && node.parent.children.length > 0) ? [node.attributes] : emptyArray; } const args = node.arguments || emptyArray; const spreadIndex = getSpreadArgumentIndex(args); if (spreadIndex >= 0) { // Create synthetic arguments from spreads of tuple types. const effectiveArgs = args.slice(0, spreadIndex); for (let i = spreadIndex; i < args.length; i++) { const arg = args[i]; // We can call checkExpressionCached because spread expressions never have a contextual type. const spreadType = arg.kind === SyntaxKind.SpreadElement && (flowLoopCount ? checkExpression((arg).expression) : checkExpressionCached((arg).expression)); if (spreadType && isTupleType(spreadType)) { forEach(getTypeArguments(spreadType), (t, i) => { const flags = spreadType.target.elementFlags[i]; const syntheticArg = createSyntheticExpression(arg, flags & ElementFlags.Rest ? createArrayType(t) : t, !!(flags & ElementFlags.Variable), spreadType.target.labeledElementDeclarations?.[i]); effectiveArgs.push(syntheticArg); }); } else { effectiveArgs.push(arg); } } return effectiveArgs; } return args; } /** * Returns the synthetic argument list for a decorator invocation. */ function getEffectiveDecoratorArguments(node: Decorator): readonly Expression[] { const parent = node.parent; const expr = node.expression; switch (parent.kind) { case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: // For a class decorator, the `target` is the type of the class (e.g. the // "static" or "constructor" side of the class). return [ createSyntheticExpression(expr, getTypeOfSymbol(getSymbolOfNode(parent))) ]; case SyntaxKind.Parameter: // A parameter declaration decorator will have three arguments (see // `ParameterDecorator` in core.d.ts). const func = parent.parent; return [ createSyntheticExpression(expr, parent.parent.kind === SyntaxKind.Constructor ? getTypeOfSymbol(getSymbolOfNode(func)) : errorType), createSyntheticExpression(expr, anyType), createSyntheticExpression(expr, numberType) ]; case SyntaxKind.PropertyDeclaration: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: // A method or accessor declaration decorator will have two or three arguments (see // `PropertyDecorator` and `MethodDecorator` in core.d.ts). If we are emitting decorators // for ES3, we will only pass two arguments. const hasPropDesc = parent.kind !== SyntaxKind.PropertyDeclaration && languageVersion !== ScriptTarget.ES3; return [ createSyntheticExpression(expr, getParentTypeOfClassElement(parent)), createSyntheticExpression(expr, getClassElementPropertyKeyType(parent)), createSyntheticExpression(expr, hasPropDesc ? createTypedPropertyDescriptorType(getTypeOfNode(parent)) : anyType) ]; } return Debug.fail(); } /** * Returns the argument count for a decorator node that works like a function invocation. */ function getDecoratorArgumentCount(node: Decorator, signature: Signature) { switch (node.parent.kind) { case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: return 1; case SyntaxKind.PropertyDeclaration: return 2; case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: // For ES3 or decorators with only two parameters we supply only two arguments return languageVersion === ScriptTarget.ES3 || signature.parameters.length <= 2 ? 2 : 3; case SyntaxKind.Parameter: return 3; default: return Debug.fail(); } } function getDiagnosticSpanForCallNode(node: CallExpression, doNotIncludeArguments?: boolean) { let start: number; let length: number; const sourceFile = getSourceFileOfNode(node); if (isPropertyAccessExpression(node.expression)) { const nameSpan = getErrorSpanForNode(sourceFile, node.expression.name); start = nameSpan.start; length = doNotIncludeArguments ? nameSpan.length : node.end - start; } else { const expressionSpan = getErrorSpanForNode(sourceFile, node.expression); start = expressionSpan.start; length = doNotIncludeArguments ? expressionSpan.length : node.end - start; } return { start, length, sourceFile }; } function getDiagnosticForCallNode(node: CallLikeExpression, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation { if (isCallExpression(node)) { const { sourceFile, start, length } = getDiagnosticSpanForCallNode(node); return createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2, arg3); } else { return createDiagnosticForNode(node, message, arg0, arg1, arg2, arg3); } } function isPromiseResolveArityError(node: CallLikeExpression) { if (!isCallExpression(node) || !isIdentifier(node.expression)) return false; const symbol = resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, undefined, undefined, false); const decl = symbol?.valueDeclaration; if (!decl || !isParameter(decl) || !isFunctionExpressionOrArrowFunction(decl.parent) || !isNewExpression(decl.parent.parent) || !isIdentifier(decl.parent.parent.expression)) { return false; } const globalPromiseSymbol = getGlobalPromiseConstructorSymbol(/*reportErrors*/ false); if (!globalPromiseSymbol) return false; const constructorSymbol = getSymbolAtLocation(decl.parent.parent.expression, /*ignoreErrors*/ true); return constructorSymbol === globalPromiseSymbol; } function getArgumentArityError(node: CallLikeExpression, signatures: readonly Signature[], args: readonly Expression[]) { let min = Number.POSITIVE_INFINITY; let max = Number.NEGATIVE_INFINITY; let belowArgCount = Number.NEGATIVE_INFINITY; let aboveArgCount = Number.POSITIVE_INFINITY; let argCount = args.length; let closestSignature: Signature | undefined; for (const sig of signatures) { const minCount = getMinArgumentCount(sig); const maxCount = getParameterCount(sig); if (minCount < argCount && minCount > belowArgCount) belowArgCount = minCount; if (argCount < maxCount && maxCount < aboveArgCount) aboveArgCount = maxCount; if (minCount < min) { min = minCount; closestSignature = sig; } max = Math.max(max, maxCount); } const hasRestParameter = some(signatures, hasEffectiveRestParameter); const paramRange = hasRestParameter ? min : min < max ? min + "-" + max : min; const hasSpreadArgument = getSpreadArgumentIndex(args) > -1; if (argCount <= max && hasSpreadArgument) { argCount--; } let spanArray: NodeArray; let related: DiagnosticWithLocation | undefined; const error = hasRestParameter || hasSpreadArgument ? hasRestParameter && hasSpreadArgument ? Diagnostics.Expected_at_least_0_arguments_but_got_1_or_more : hasRestParameter ? Diagnostics.Expected_at_least_0_arguments_but_got_1 : Diagnostics.Expected_0_arguments_but_got_1_or_more : paramRange === 1 && argCount === 0 && isPromiseResolveArityError(node) ? Diagnostics.Expected_0_arguments_but_got_1_Did_you_forget_to_include_void_in_your_type_argument_to_Promise : Diagnostics.Expected_0_arguments_but_got_1; if (closestSignature && getMinArgumentCount(closestSignature) > argCount && closestSignature.declaration) { const paramDecl = closestSignature.declaration.parameters[closestSignature.thisParameter ? argCount + 1 : argCount]; if (paramDecl) { related = createDiagnosticForNode( paramDecl, isBindingPattern(paramDecl.name) ? Diagnostics.An_argument_matching_this_binding_pattern_was_not_provided : isRestParameter(paramDecl) ? Diagnostics.Arguments_for_the_rest_parameter_0_were_not_provided : Diagnostics.An_argument_for_0_was_not_provided, !paramDecl.name ? argCount : !isBindingPattern(paramDecl.name) ? idText(getFirstIdentifier(paramDecl.name)) : undefined ); } } if (min < argCount && argCount < max) { return getDiagnosticForCallNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, argCount, belowArgCount, aboveArgCount); } if (!hasSpreadArgument && argCount < min) { const diagnostic = getDiagnosticForCallNode(node, error, paramRange, argCount); return related ? addRelatedInfo(diagnostic, related) : diagnostic; } if (hasRestParameter || hasSpreadArgument) { spanArray = factory.createNodeArray(args); if (hasSpreadArgument && argCount) { const nextArg = elementAt(args, getSpreadArgumentIndex(args) + 1) || undefined; spanArray = factory.createNodeArray(args.slice(max > argCount && nextArg ? args.indexOf(nextArg) : Math.min(max, args.length - 1))); } } else { spanArray = factory.createNodeArray(args.slice(max)); } const pos = first(spanArray).pos; let end = last(spanArray).end; if (end === pos) { end++; } setTextRangePosEnd(spanArray, pos, end); const diagnostic = createDiagnosticForNodeArray( getSourceFileOfNode(node), spanArray, error, paramRange, argCount); return related ? addRelatedInfo(diagnostic, related) : diagnostic; } function getTypeArgumentArityError(node: Node, signatures: readonly Signature[], typeArguments: NodeArray) { const argCount = typeArguments.length; // No overloads exist if (signatures.length === 1) { const sig = signatures[0]; const min = getMinTypeArgumentCount(sig.typeParameters); const max = length(sig.typeParameters); return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, min < max ? min + "-" + max : min , argCount); } // Overloads exist let belowArgCount = -Infinity; let aboveArgCount = Infinity; for (const sig of signatures) { const min = getMinTypeArgumentCount(sig.typeParameters); const max = length(sig.typeParameters); if (min > argCount) { aboveArgCount = Math.min(aboveArgCount, min); } else if (max < argCount) { belowArgCount = Math.max(belowArgCount, max); } } if (belowArgCount !== -Infinity && aboveArgCount !== Infinity) { return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.No_overload_expects_0_type_arguments_but_overloads_do_exist_that_expect_either_1_or_2_type_arguments, argCount, belowArgCount, aboveArgCount); } return createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Expected_0_type_arguments_but_got_1, belowArgCount === -Infinity ? aboveArgCount : belowArgCount, argCount); } function resolveCall(node: CallLikeExpression, signatures: readonly Signature[], candidatesOutArray: Signature[] | undefined, checkMode: CheckMode, callChainFlags: SignatureFlags, fallbackError?: DiagnosticMessage): Signature { const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; const isDecorator = node.kind === SyntaxKind.Decorator; const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); const reportErrors = !candidatesOutArray && produceDiagnostics; let typeArguments: NodeArray | undefined; if (!isDecorator) { typeArguments = (node).typeArguments; // We already perform checking on the type arguments on the class declaration itself. if (isTaggedTemplate || isJsxOpeningOrSelfClosingElement || (node).expression.kind !== SyntaxKind.SuperKeyword) { forEach(typeArguments, checkSourceElement); } } const candidates = candidatesOutArray || []; // reorderCandidates fills up the candidates array directly reorderCandidates(signatures, candidates, callChainFlags); if (!candidates.length) { if (reportErrors) { diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures)); } return resolveErrorCall(node); } const args = getEffectiveCallArguments(node); // The excludeArgument array contains true for each context sensitive argument (an argument // is context sensitive it is susceptible to a one-time permanent contextual typing). // // The idea is that we will perform type argument inference & assignability checking once // without using the susceptible parameters that are functions, and once more for those // parameters, contextually typing each as we go along. // // For a tagged template, then the first argument be 'undefined' if necessary because it // represents a TemplateStringsArray. // // For a decorator, no arguments are susceptible to contextual typing due to the fact // decorators are applied to a declaration by the emitter, and not to an expression. const isSingleNonGenericCandidate = candidates.length === 1 && !candidates[0].typeParameters; let argCheckMode = !isDecorator && !isSingleNonGenericCandidate && some(args, isContextSensitive) ? CheckMode.SkipContextSensitive : CheckMode.Normal; // The following variables are captured and modified by calls to chooseOverload. // If overload resolution or type argument inference fails, we want to report the // best error possible. The best error is one which says that an argument was not // assignable to a parameter. This implies that everything else about the overload // was fine. So if there is any overload that is only incorrect because of an // argument, we will report an error on that one. // // function foo(s: string): void; // function foo(n: number): void; // Report argument error on this overload // function foo(): void; // foo(true); // // If none of the overloads even made it that far, there are two possibilities. // There was a problem with type arguments for some overload, in which case // report an error on that. Or none of the overloads even had correct arity, // in which case give an arity error. // // function foo(x: T): void; // Report type argument error // function foo(): void; // foo(0); // let candidatesForArgumentError: Signature[] | undefined; let candidateForArgumentArityError: Signature | undefined; let candidateForTypeArgumentError: Signature | undefined; let result: Signature | undefined; // If we are in signature help, a trailing comma indicates that we intend to provide another argument, // so we will only accept overloads with arity at least 1 higher than the current number of provided arguments. const signatureHelpTrailingComma = !!(checkMode & CheckMode.IsForSignatureHelp) && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma; // Section 4.12.1: // if the candidate list contains one or more signatures for which the type of each argument // expression is a subtype of each corresponding parameter type, the return type of the first // of those signatures becomes the return type of the function call. // Otherwise, the return type of the first signature in the candidate list becomes the return // type of the function call. // // Whether the call is an error is determined by assignability of the arguments. The subtype pass // is just important for choosing the best signature. So in the case where there is only one // signature, the subtype pass is useless. So skipping it is an optimization. if (candidates.length > 1) { result = chooseOverload(candidates, subtypeRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma); } if (!result) { result = chooseOverload(candidates, assignableRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma); } if (result) { return result; } // No signatures were applicable. Now report errors based on the last applicable signature with // no arguments excluded from assignability checks. // If candidate is undefined, it means that no candidates had a suitable arity. In that case, // skip the checkApplicableSignature check. if (reportErrors) { if (candidatesForArgumentError) { if (candidatesForArgumentError.length === 1 || candidatesForArgumentError.length > 3) { const last = candidatesForArgumentError[candidatesForArgumentError.length - 1]; let chain: DiagnosticMessageChain | undefined; if (candidatesForArgumentError.length > 3) { chain = chainDiagnosticMessages(chain, Diagnostics.The_last_overload_gave_the_following_error); chain = chainDiagnosticMessages(chain, Diagnostics.No_overload_matches_this_call); } const diags = getSignatureApplicabilityError(node, args, last, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, () => chain); if (diags) { for (const d of diags) { if (last.declaration && candidatesForArgumentError.length > 3) { addRelatedInfo(d, createDiagnosticForNode(last.declaration, Diagnostics.The_last_overload_is_declared_here)); } addImplementationSuccessElaboration(last, d); diagnostics.add(d); } } else { Debug.fail("No error for last overload signature"); } } else { const allDiagnostics: (readonly DiagnosticRelatedInformation[])[] = []; let max = 0; let min = Number.MAX_VALUE; let minIndex = 0; let i = 0; for (const c of candidatesForArgumentError) { const chain = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Overload_0_of_1_2_gave_the_following_error, i + 1, candidates.length, signatureToString(c)); const diags = getSignatureApplicabilityError(node, args, c, assignableRelation, CheckMode.Normal, /*reportErrors*/ true, chain); if (diags) { if (diags.length <= min) { min = diags.length; minIndex = i; } max = Math.max(max, diags.length); allDiagnostics.push(diags); } else { Debug.fail("No error for 3 or fewer overload signatures"); } i++; } const diags = max > 1 ? allDiagnostics[minIndex] : flatten(allDiagnostics); Debug.assert(diags.length > 0, "No errors reported for 3 or fewer overload signatures"); const chain = chainDiagnosticMessages( map(diags, d => typeof d.messageText === "string" ? (d as DiagnosticMessageChain) : d.messageText), Diagnostics.No_overload_matches_this_call); // The below is a spread to guarantee we get a new (mutable) array - our `flatMap` helper tries to do "smart" optimizations where it reuses input // arrays and the emptyArray singleton where possible, which is decidedly not what we want while we're still constructing this diagnostic const related = [...flatMap(diags, d => (d as Diagnostic).relatedInformation) as DiagnosticRelatedInformation[]]; let diag: Diagnostic; if (every(diags, d => d.start === diags[0].start && d.length === diags[0].length && d.file === diags[0].file)) { const { file, start, length } = diags[0]; diag = { file, start, length, code: chain.code, category: chain.category, messageText: chain, relatedInformation: related }; } else { diag = createDiagnosticForNodeFromMessageChain(node, chain, related); } addImplementationSuccessElaboration(candidatesForArgumentError[0], diag); diagnostics.add(diag); } } else if (candidateForArgumentArityError) { diagnostics.add(getArgumentArityError(node, [candidateForArgumentArityError], args)); } else if (candidateForTypeArgumentError) { checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression | JsxOpeningLikeElement).typeArguments!, /*reportErrors*/ true, fallbackError); } else { const signaturesWithCorrectTypeArgumentArity = filter(signatures, s => hasCorrectTypeArgumentArity(s, typeArguments)); if (signaturesWithCorrectTypeArgumentArity.length === 0) { diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments!)); } else if (!isDecorator) { diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args)); } else if (fallbackError) { diagnostics.add(getDiagnosticForCallNode(node, fallbackError)); } } } return getCandidateForOverloadFailure(node, candidates, args, !!candidatesOutArray); function addImplementationSuccessElaboration(failed: Signature, diagnostic: Diagnostic) { const oldCandidatesForArgumentError = candidatesForArgumentError; const oldCandidateForArgumentArityError = candidateForArgumentArityError; const oldCandidateForTypeArgumentError = candidateForTypeArgumentError; const failedSignatureDeclarations = failed.declaration?.symbol?.declarations || emptyArray; const isOverload = failedSignatureDeclarations.length > 1; const implDecl = isOverload ? find(failedSignatureDeclarations, d => isFunctionLikeDeclaration(d) && nodeIsPresent(d.body)) : undefined; if (implDecl) { const candidate = getSignatureFromDeclaration(implDecl as FunctionLikeDeclaration); const isSingleNonGenericCandidate = !candidate.typeParameters; if (chooseOverload([candidate], assignableRelation, isSingleNonGenericCandidate)) { addRelatedInfo(diagnostic, createDiagnosticForNode(implDecl, Diagnostics.The_call_would_have_succeeded_against_this_implementation_but_implementation_signatures_of_overloads_are_not_externally_visible)); } } candidatesForArgumentError = oldCandidatesForArgumentError; candidateForArgumentArityError = oldCandidateForArgumentArityError; candidateForTypeArgumentError = oldCandidateForTypeArgumentError; } function chooseOverload(candidates: Signature[], relation: ESMap, isSingleNonGenericCandidate: boolean, signatureHelpTrailingComma = false) { candidatesForArgumentError = undefined; candidateForArgumentArityError = undefined; candidateForTypeArgumentError = undefined; if (isSingleNonGenericCandidate) { const candidate = candidates[0]; if (some(typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { return undefined; } if (getSignatureApplicabilityError(node, args, candidate, relation, CheckMode.Normal, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { candidatesForArgumentError = [candidate]; return undefined; } return candidate; } for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) { const candidate = candidates[candidateIndex]; if (!hasCorrectTypeArgumentArity(candidate, typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) { continue; } let checkCandidate: Signature; let inferenceContext: InferenceContext | undefined; if (candidate.typeParameters) { let typeArgumentTypes: Type[] | undefined; if (some(typeArguments)) { typeArgumentTypes = checkTypeArguments(candidate, typeArguments, /*reportErrors*/ false); if (!typeArgumentTypes) { candidateForTypeArgumentError = candidate; continue; } } else { inferenceContext = createInferenceContext(candidate.typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode | CheckMode.SkipGenericFunctions, inferenceContext); argCheckMode |= inferenceContext.flags & InferenceFlags.SkippedGenericFunction ? CheckMode.SkipGenericFunctions : CheckMode.Normal; } checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters); // If the original signature has a generic rest type, instantiation may produce a // signature with different arity and we need to perform another arity check. if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) { candidateForArgumentArityError = checkCandidate; continue; } } else { checkCandidate = candidate; } if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { // Give preference to error candidates that have no rest parameters (as they are more specific) (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); continue; } if (argCheckMode) { // If one or more context sensitive arguments were excluded, we start including // them now (and keeping do so for any subsequent candidates) and perform a second // round of type inference and applicability checking for this particular candidate. argCheckMode = CheckMode.Normal; if (inferenceContext) { const typeArgumentTypes = inferTypeArguments(node, candidate, args, argCheckMode, inferenceContext); checkCandidate = getSignatureInstantiation(candidate, typeArgumentTypes, isInJSFile(candidate.declaration), inferenceContext && inferenceContext.inferredTypeParameters); // If the original signature has a generic rest type, instantiation may produce a // signature with different arity and we need to perform another arity check. if (getNonArrayRestType(candidate) && !hasCorrectArity(node, args, checkCandidate, signatureHelpTrailingComma)) { candidateForArgumentArityError = checkCandidate; continue; } } if (getSignatureApplicabilityError(node, args, checkCandidate, relation, argCheckMode, /*reportErrors*/ false, /*containingMessageChain*/ undefined)) { // Give preference to error candidates that have no rest parameters (as they are more specific) (candidatesForArgumentError || (candidatesForArgumentError = [])).push(checkCandidate); continue; } } candidates[candidateIndex] = checkCandidate; return checkCandidate; } return undefined; } } // No signature was applicable. We have already reported the errors for the invalid signature. function getCandidateForOverloadFailure( node: CallLikeExpression, candidates: Signature[], args: readonly Expression[], hasCandidatesOutArray: boolean, ): Signature { Debug.assert(candidates.length > 0); // Else should not have called this. checkNodeDeferred(node); // Normally we will combine overloads. Skip this if they have type parameters since that's hard to combine. // Don't do this if there is a `candidatesOutArray`, // because then we want the chosen best candidate to be one of the overloads, not a combination. return hasCandidatesOutArray || candidates.length === 1 || candidates.some(c => !!c.typeParameters) ? pickLongestCandidateSignature(node, candidates, args) : createUnionOfSignaturesForOverloadFailure(candidates); } function createUnionOfSignaturesForOverloadFailure(candidates: readonly Signature[]): Signature { const thisParameters = mapDefined(candidates, c => c.thisParameter); let thisParameter: Symbol | undefined; if (thisParameters.length) { thisParameter = createCombinedSymbolFromTypes(thisParameters, thisParameters.map(getTypeOfParameter)); } const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters); const parameters: Symbol[] = []; for (let i = 0; i < maxNonRestParam; i++) { const symbols = mapDefined(candidates, s => signatureHasRestParameter(s) ? i < s.parameters.length - 1 ? s.parameters[i] : last(s.parameters) : i < s.parameters.length ? s.parameters[i] : undefined); Debug.assert(symbols.length !== 0); parameters.push(createCombinedSymbolFromTypes(symbols, mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i)))); } const restParameterSymbols = mapDefined(candidates, c => signatureHasRestParameter(c) ? last(c.parameters) : undefined); let flags = SignatureFlags.None; if (restParameterSymbols.length !== 0) { const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), UnionReduction.Subtype)); parameters.push(createCombinedSymbolForOverloadFailure(restParameterSymbols, type)); flags |= SignatureFlags.HasRestParameter; } if (candidates.some(signatureHasLiteralTypes)) { flags |= SignatureFlags.HasLiteralTypes; } return createSignature( candidates[0].declaration, /*typeParameters*/ undefined, // Before calling this we tested for `!candidates.some(c => !!c.typeParameters)`. thisParameter, parameters, /*resolvedReturnType*/ getIntersectionType(candidates.map(getReturnTypeOfSignature)), /*typePredicate*/ undefined, minArgumentCount, flags); } function getNumNonRestParameters(signature: Signature): number { const numParams = signature.parameters.length; return signatureHasRestParameter(signature) ? numParams - 1 : numParams; } function createCombinedSymbolFromTypes(sources: readonly Symbol[], types: Type[]): Symbol { return createCombinedSymbolForOverloadFailure(sources, getUnionType(types, UnionReduction.Subtype)); } function createCombinedSymbolForOverloadFailure(sources: readonly Symbol[], type: Type): Symbol { // This function is currently only used for erroneous overloads, so it's good enough to just use the first source. return createSymbolWithType(first(sources), type); } function pickLongestCandidateSignature(node: CallLikeExpression, candidates: Signature[], args: readonly Expression[]): Signature { // Pick the longest signature. This way we can get a contextual type for cases like: // declare function f(a: { xa: number; xb: number; }, b: number); // f({ | // Also, use explicitly-supplied type arguments if they are provided, so we can get a contextual signature in cases like: // declare function f(k: keyof T); // f(" const bestIndex = getLongestCandidateIndex(candidates, apparentArgumentCount === undefined ? args.length : apparentArgumentCount); const candidate = candidates[bestIndex]; const { typeParameters } = candidate; if (!typeParameters) { return candidate; } const typeArgumentNodes: readonly TypeNode[] | undefined = callLikeExpressionMayHaveTypeArguments(node) ? node.typeArguments : undefined; const instantiated = typeArgumentNodes ? createSignatureInstantiation(candidate, getTypeArgumentsFromNodes(typeArgumentNodes, typeParameters, isInJSFile(node))) : inferSignatureInstantiationForOverloadFailure(node, typeParameters, candidate, args); candidates[bestIndex] = instantiated; return instantiated; } function getTypeArgumentsFromNodes(typeArgumentNodes: readonly TypeNode[], typeParameters: readonly TypeParameter[], isJs: boolean): readonly Type[] { const typeArguments = typeArgumentNodes.map(getTypeOfNode); while (typeArguments.length > typeParameters.length) { typeArguments.pop(); } while (typeArguments.length < typeParameters.length) { typeArguments.push(getConstraintOfTypeParameter(typeParameters[typeArguments.length]) || getDefaultTypeArgumentType(isJs)); } return typeArguments; } function inferSignatureInstantiationForOverloadFailure(node: CallLikeExpression, typeParameters: readonly TypeParameter[], candidate: Signature, args: readonly Expression[]): Signature { const inferenceContext = createInferenceContext(typeParameters, candidate, /*flags*/ isInJSFile(node) ? InferenceFlags.AnyDefault : InferenceFlags.None); const typeArgumentTypes = inferTypeArguments(node, candidate, args, CheckMode.SkipContextSensitive | CheckMode.SkipGenericFunctions, inferenceContext); return createSignatureInstantiation(candidate, typeArgumentTypes); } function getLongestCandidateIndex(candidates: Signature[], argsCount: number): number { let maxParamsIndex = -1; let maxParams = -1; for (let i = 0; i < candidates.length; i++) { const candidate = candidates[i]; const paramCount = getParameterCount(candidate); if (hasEffectiveRestParameter(candidate) || paramCount >= argsCount) { return i; } if (paramCount > maxParams) { maxParams = paramCount; maxParamsIndex = i; } } return maxParamsIndex; } function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { if (node.expression.kind === SyntaxKind.SuperKeyword) { const superType = checkSuperExpression(node.expression); if (isTypeAny(superType)) { for (const arg of node.arguments) { checkExpression(arg); // Still visit arguments so they get marked for visibility, etc } return anySignature; } if (superType !== errorType) { // In super call, the candidate signatures are the matching arity signatures of the base constructor function instantiated // with the type arguments specified in the extends clause. const baseTypeNode = getEffectiveBaseTypeNode(getContainingClass(node)!); if (baseTypeNode) { const baseConstructors = getInstantiatedConstructorsForTypeArguments(superType, baseTypeNode.typeArguments, baseTypeNode); return resolveCall(node, baseConstructors, candidatesOutArray, checkMode, SignatureFlags.None); } } return resolveUntypedCall(node); } let callChainFlags: SignatureFlags; let funcType = checkExpression(node.expression); if (isCallChain(node)) { const nonOptionalType = getOptionalExpressionType(funcType, node.expression); callChainFlags = nonOptionalType === funcType ? SignatureFlags.None : isOutermostOptionalChain(node) ? SignatureFlags.IsOuterCallChain : SignatureFlags.IsInnerCallChain; funcType = nonOptionalType; } else { callChainFlags = SignatureFlags.None; } funcType = checkNonNullTypeWithReporter( funcType, node.expression, reportCannotInvokePossiblyNullOrUndefinedError ); if (funcType === silentNeverType) { return silentNeverSignature; } const apparentType = getApparentType(funcType); if (apparentType === errorType) { // Another error has already been reported return resolveErrorCall(node); } // Technically, this signatures list may be incomplete. We are taking the apparent type, // but we are not including call signatures that may have been added to the Object or // Function interface, since they have none by default. This is a bit of a leap of faith // that the user will not add any. const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; // TS 1.0 Spec: 4.12 // In an untyped function call no TypeArgs are permitted, Args can be any argument list, no contextual // types are provided for the argument expressions, and the result is always of type Any. if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { // The unknownType indicates that an error already occurred (and was reported). No // need to report another error in this case. if (funcType !== errorType && node.typeArguments) { error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); } return resolveUntypedCall(node); } // If FuncExpr's apparent type(section 3.8.1) is a function type, the call is a typed function call. // TypeScript employs overload resolution in typed function calls in order to support functions // with multiple call signatures. if (!callSignatures.length) { if (numConstructSignatures) { error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); } else { let relatedInformation: DiagnosticRelatedInformation | undefined; if (node.arguments.length === 1) { const text = getSourceFileOfNode(node).text; if (isLineBreak(text.charCodeAt(skipTrivia(text, node.expression.end, /* stopAfterLineBreak */ true) - 1))) { relatedInformation = createDiagnosticForNode(node.expression, Diagnostics.Are_you_missing_a_semicolon); } } invocationError(node.expression, apparentType, SignatureKind.Call, relatedInformation); } return resolveErrorCall(node); } // When a call to a generic function is an argument to an outer call to a generic function for which // inference is in process, we have a choice to make. If the inner call relies on inferences made from // its contextual type to its return type, deferring the inner call processing allows the best possible // contextual type to accumulate. But if the outer call relies on inferences made from the return type of // the inner call, the inner call should be processed early. There's no sure way to know which choice is // right (only a full unification algorithm can determine that), so we resort to the following heuristic: // If no type arguments are specified in the inner call and at least one call signature is generic and // returns a function type, we choose to defer processing. This narrowly permits function composition // operators to flow inferences through return types, but otherwise processes calls right away. We // use the resolvingSignature singleton to indicate that we deferred processing. This result will be // propagated out and eventually turned into nonInferrableType (a type that is assignable to anything and // from which we never make inferences). if (checkMode & CheckMode.SkipGenericFunctions && !node.typeArguments && callSignatures.some(isGenericFunctionReturningFunction)) { skippedGenericFunction(node, checkMode); return resolvingSignature; } // If the function is explicitly marked with `@class`, then it must be constructed. if (callSignatures.some(sig => isInJSFile(sig.declaration) && !!getJSDocClassTag(sig.declaration!))) { error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType)); return resolveErrorCall(node); } return resolveCall(node, callSignatures, candidatesOutArray, checkMode, callChainFlags); } function isGenericFunctionReturningFunction(signature: Signature) { return !!(signature.typeParameters && isFunctionType(getReturnTypeOfSignature(signature))); } /** * TS 1.0 spec: 4.12 * If FuncExpr is of type Any, or of an object type that has no call or construct signatures * but is a subtype of the Function interface, the call is an untyped function call. */ function isUntypedFunctionCall(funcType: Type, apparentFuncType: Type, numCallSignatures: number, numConstructSignatures: number): boolean { // We exclude union types because we may have a union of function types that happen to have no common signatures. return isTypeAny(funcType) || isTypeAny(apparentFuncType) && !!(funcType.flags & TypeFlags.TypeParameter) || !numCallSignatures && !numConstructSignatures && !(apparentFuncType.flags & (TypeFlags.Union | TypeFlags.Never)) && isTypeAssignableTo(funcType, globalFunctionType); } function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { if (node.arguments && languageVersion < ScriptTarget.ES5) { const spreadIndex = getSpreadArgumentIndex(node.arguments); if (spreadIndex >= 0) { error(node.arguments[spreadIndex], Diagnostics.Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_5_and_higher); } } let expressionType = checkNonNullExpression(node.expression); if (expressionType === silentNeverType) { return silentNeverSignature; } // If expressionType's apparent type(section 3.8.1) is an object type with one or // more construct signatures, the expression is processed in the same manner as a // function call, but using the construct signatures as the initial set of candidate // signatures for overload resolution. The result type of the function call becomes // the result type of the operation. expressionType = getApparentType(expressionType); if (expressionType === errorType) { // Another error has already been reported return resolveErrorCall(node); } // TS 1.0 spec: 4.11 // If expressionType is of type Any, Args can be any argument // list and the result of the operation is of type Any. if (isTypeAny(expressionType)) { if (node.typeArguments) { error(node, Diagnostics.Untyped_function_calls_may_not_accept_type_arguments); } return resolveUntypedCall(node); } // Technically, this signatures list may be incomplete. We are taking the apparent type, // but we are not including construct signatures that may have been added to the Object or // Function interface, since they have none by default. This is a bit of a leap of faith // that the user will not add any. const constructSignatures = getSignaturesOfType(expressionType, SignatureKind.Construct); if (constructSignatures.length) { if (!isConstructorAccessible(node, constructSignatures[0])) { return resolveErrorCall(node); } // If the expression is a class of abstract type, then it cannot be instantiated. // Note, only class declarations can be declared abstract. // In the case of a merged class-module or class-interface declaration, // only the class declaration node will have the Abstract flag set. const valueDecl = expressionType.symbol && getClassLikeDeclarationOfSymbol(expressionType.symbol); if (valueDecl && hasSyntacticModifier(valueDecl, ModifierFlags.Abstract)) { error(node, Diagnostics.Cannot_create_an_instance_of_an_abstract_class); return resolveErrorCall(node); } return resolveCall(node, constructSignatures, candidatesOutArray, checkMode, SignatureFlags.None); } // If expressionType's apparent type is an object type with no construct signatures but // one or more call signatures, the expression is processed as a function call. A compile-time // error occurs if the result of the function call is not Void. The type of the result of the // operation is Any. It is an error to have a Void this type. const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call); if (callSignatures.length) { const signature = resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); if (!noImplicitAny) { if (signature.declaration && !isJSConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) { error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword); } if (getThisTypeOfSignature(signature) === voidType) { error(node, Diagnostics.A_function_that_is_called_with_the_new_keyword_cannot_have_a_this_type_that_is_void); } } return signature; } invocationError(node.expression, expressionType, SignatureKind.Construct); return resolveErrorCall(node); } function typeHasProtectedAccessibleBase(target: Symbol, type: InterfaceType): boolean { const baseTypes = getBaseTypes(type); if (!length(baseTypes)) { return false; } const firstBase = baseTypes[0]; if (firstBase.flags & TypeFlags.Intersection) { const types = (firstBase as IntersectionType).types; const mixinFlags = findMixins(types); let i = 0; for (const intersectionMember of (firstBase as IntersectionType).types) { // We want to ignore mixin ctors if (!mixinFlags[i]) { if (getObjectFlags(intersectionMember) & (ObjectFlags.Class | ObjectFlags.Interface)) { if (intersectionMember.symbol === target) { return true; } if (typeHasProtectedAccessibleBase(target, intersectionMember as InterfaceType)) { return true; } } } i++; } return false; } if (firstBase.symbol === target) { return true; } return typeHasProtectedAccessibleBase(target, firstBase as InterfaceType); } function isConstructorAccessible(node: NewExpression, signature: Signature) { if (!signature || !signature.declaration) { return true; } const declaration = signature.declaration; const modifiers = getSelectedEffectiveModifierFlags(declaration, ModifierFlags.NonPublicAccessibilityModifier); // (1) Public constructors and (2) constructor functions are always accessible. if (!modifiers || declaration.kind !== SyntaxKind.Constructor) { return true; } const declaringClassDeclaration = getClassLikeDeclarationOfSymbol(declaration.parent.symbol)!; const declaringClass = getDeclaredTypeOfSymbol(declaration.parent.symbol); // A private or protected constructor can only be instantiated within its own class (or a subclass, for protected) if (!isNodeWithinClass(node, declaringClassDeclaration)) { const containingClass = getContainingClass(node); if (containingClass && modifiers & ModifierFlags.Protected) { const containingType = getTypeOfNode(containingClass); if (typeHasProtectedAccessibleBase(declaration.parent.symbol, containingType as InterfaceType)) { return true; } } if (modifiers & ModifierFlags.Private) { error(node, Diagnostics.Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration, typeToString(declaringClass)); } if (modifiers & ModifierFlags.Protected) { error(node, Diagnostics.Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration, typeToString(declaringClass)); } return false; } return true; } function invocationErrorDetails(errorTarget: Node, apparentType: Type, kind: SignatureKind): { messageChain: DiagnosticMessageChain, relatedMessage: DiagnosticMessage | undefined } { let errorInfo: DiagnosticMessageChain | undefined; const isCall = kind === SignatureKind.Call; const awaitedType = getAwaitedType(apparentType); const maybeMissingAwait = awaitedType && getSignaturesOfType(awaitedType, kind).length > 0; if (apparentType.flags & TypeFlags.Union) { const types = (apparentType as UnionType).types; let hasSignatures = false; for (const constituent of types) { const signatures = getSignaturesOfType(constituent, kind); if (signatures.length !== 0) { hasSignatures = true; if (errorInfo) { // Bail early if we already have an error, no chance of "No constituent of type is callable" break; } } else { // Error on the first non callable constituent only if (!errorInfo) { errorInfo = chainDiagnosticMessages( errorInfo, isCall ? Diagnostics.Type_0_has_no_call_signatures : Diagnostics.Type_0_has_no_construct_signatures, typeToString(constituent) ); errorInfo = chainDiagnosticMessages( errorInfo, isCall ? Diagnostics.Not_all_constituents_of_type_0_are_callable : Diagnostics.Not_all_constituents_of_type_0_are_constructable, typeToString(apparentType) ); } if (hasSignatures) { // Bail early if we already found a siganture, no chance of "No constituent of type is callable" break; } } } if (!hasSignatures) { errorInfo = chainDiagnosticMessages( /* detials */ undefined, isCall ? Diagnostics.No_constituent_of_type_0_is_callable : Diagnostics.No_constituent_of_type_0_is_constructable, typeToString(apparentType) ); } if (!errorInfo) { errorInfo = chainDiagnosticMessages( errorInfo, isCall ? Diagnostics.Each_member_of_the_union_type_0_has_signatures_but_none_of_those_signatures_are_compatible_with_each_other : Diagnostics.Each_member_of_the_union_type_0_has_construct_signatures_but_none_of_those_signatures_are_compatible_with_each_other, typeToString(apparentType) ); } } else { errorInfo = chainDiagnosticMessages( errorInfo, isCall ? Diagnostics.Type_0_has_no_call_signatures : Diagnostics.Type_0_has_no_construct_signatures, typeToString(apparentType) ); } let headMessage = isCall ? Diagnostics.This_expression_is_not_callable : Diagnostics.This_expression_is_not_constructable; // Diagnose get accessors incorrectly called as functions if (isCallExpression(errorTarget.parent) && errorTarget.parent.arguments.length === 0) { const { resolvedSymbol } = getNodeLinks(errorTarget); if (resolvedSymbol && resolvedSymbol.flags & SymbolFlags.GetAccessor) { headMessage = Diagnostics.This_expression_is_not_callable_because_it_is_a_get_accessor_Did_you_mean_to_use_it_without; } } return { messageChain: chainDiagnosticMessages(errorInfo, headMessage), relatedMessage: maybeMissingAwait ? Diagnostics.Did_you_forget_to_use_await : undefined, }; } function invocationError(errorTarget: Node, apparentType: Type, kind: SignatureKind, relatedInformation?: DiagnosticRelatedInformation) { const { messageChain, relatedMessage: relatedInfo } = invocationErrorDetails(errorTarget, apparentType, kind); const diagnostic = createDiagnosticForNodeFromMessageChain(errorTarget, messageChain); if (relatedInfo) { addRelatedInfo(diagnostic, createDiagnosticForNode(errorTarget, relatedInfo)); } if (isCallExpression(errorTarget.parent)) { const { start, length } = getDiagnosticSpanForCallNode(errorTarget.parent, /* doNotIncludeArguments */ true); diagnostic.start = start; diagnostic.length = length; } diagnostics.add(diagnostic); invocationErrorRecovery(apparentType, kind, relatedInformation ? addRelatedInfo(diagnostic, relatedInformation) : diagnostic); } function invocationErrorRecovery(apparentType: Type, kind: SignatureKind, diagnostic: Diagnostic) { if (!apparentType.symbol) { return; } const importNode = getSymbolLinks(apparentType.symbol).originatingImport; // Create a diagnostic on the originating import if possible onto which we can attach a quickfix // An import call expression cannot be rewritten into another form to correct the error - the only solution is to use `.default` at the use-site if (importNode && !isImportCall(importNode)) { const sigs = getSignaturesOfType(getTypeOfSymbol(getSymbolLinks(apparentType.symbol).target!), kind); if (!sigs || !sigs.length) return; addRelatedInfo(diagnostic, createDiagnosticForNode(importNode, Diagnostics.Type_originates_at_this_import_A_namespace_style_import_cannot_be_called_or_constructed_and_will_cause_a_failure_at_runtime_Consider_using_a_default_import_or_import_require_here_instead) ); } } function resolveTaggedTemplateExpression(node: TaggedTemplateExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { const tagType = checkExpression(node.tag); const apparentType = getApparentType(tagType); if (apparentType === errorType) { // Another error has already been reported return resolveErrorCall(node); } const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; if (isUntypedFunctionCall(tagType, apparentType, callSignatures.length, numConstructSignatures)) { return resolveUntypedCall(node); } if (!callSignatures.length) { if (isArrayLiteralExpression(node.parent)) { const diagnostic = createDiagnosticForNode(node.tag, Diagnostics.It_is_likely_that_you_are_missing_a_comma_to_separate_these_two_template_expressions_They_form_a_tagged_template_expression_which_cannot_be_invoked); diagnostics.add(diagnostic); return resolveErrorCall(node); } invocationError(node.tag, apparentType, SignatureKind.Call); return resolveErrorCall(node); } return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None); } /** * Gets the localized diagnostic head message to use for errors when resolving a decorator as a call expression. */ function getDiagnosticHeadMessageForDecoratorResolution(node: Decorator) { switch (node.parent.kind) { case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: return Diagnostics.Unable_to_resolve_signature_of_class_decorator_when_called_as_an_expression; case SyntaxKind.Parameter: return Diagnostics.Unable_to_resolve_signature_of_parameter_decorator_when_called_as_an_expression; case SyntaxKind.PropertyDeclaration: return Diagnostics.Unable_to_resolve_signature_of_property_decorator_when_called_as_an_expression; case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: return Diagnostics.Unable_to_resolve_signature_of_method_decorator_when_called_as_an_expression; default: return Debug.fail(); } } /** * Resolves a decorator as if it were a call expression. */ function resolveDecorator(node: Decorator, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { const funcType = checkExpression(node.expression); const apparentType = getApparentType(funcType); if (apparentType === errorType) { return resolveErrorCall(node); } const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; if (isUntypedFunctionCall(funcType, apparentType, callSignatures.length, numConstructSignatures)) { return resolveUntypedCall(node); } if (isPotentiallyUncalledDecorator(node, callSignatures)) { const nodeStr = getTextOfNode(node.expression, /*includeTrivia*/ false); error(node, Diagnostics._0_accepts_too_few_arguments_to_be_used_as_a_decorator_here_Did_you_mean_to_call_it_first_and_write_0, nodeStr); return resolveErrorCall(node); } const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); if (!callSignatures.length) { const errorDetails = invocationErrorDetails(node.expression, apparentType, SignatureKind.Call); const messageChain = chainDiagnosticMessages(errorDetails.messageChain, headMessage); const diag = createDiagnosticForNodeFromMessageChain(node.expression, messageChain); if (errorDetails.relatedMessage) { addRelatedInfo(diag, createDiagnosticForNode(node.expression, errorDetails.relatedMessage)); } diagnostics.add(diag); invocationErrorRecovery(apparentType, SignatureKind.Call, diag); return resolveErrorCall(node); } return resolveCall(node, callSignatures, candidatesOutArray, checkMode, SignatureFlags.None, headMessage); } function createSignatureForJSXIntrinsic(node: JsxOpeningLikeElement, result: Type): Signature { const namespace = getJsxNamespaceAt(node); const exports = namespace && getExportsOfSymbol(namespace); // We fake up a SFC signature for each intrinsic, however a more specific per-element signature drawn from the JSX declaration // file would probably be preferable. const typeSymbol = exports && getSymbol(exports, JsxNames.Element, SymbolFlags.Type); const returnNode = typeSymbol && nodeBuilder.symbolToEntityName(typeSymbol, SymbolFlags.Type, node); const declaration = factory.createFunctionTypeNode(/*typeParameters*/ undefined, [factory.createParameterDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotdotdot*/ undefined, "props", /*questionMark*/ undefined, nodeBuilder.typeToTypeNode(result, node))], returnNode ? factory.createTypeReferenceNode(returnNode, /*typeArguments*/ undefined) : factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) ); const parameterSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, "props" as __String); parameterSymbol.type = result; return createSignature( declaration, /*typeParameters*/ undefined, /*thisParameter*/ undefined, [parameterSymbol], typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType, /*returnTypePredicate*/ undefined, 1, SignatureFlags.None ); } function resolveJsxOpeningLikeElement(node: JsxOpeningLikeElement, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { if (isJsxIntrinsicIdentifier(node.tagName)) { const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node); const fakeSignature = createSignatureForJSXIntrinsic(node, result); checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*mapper*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes); if (length(node.typeArguments)) { forEach(node.typeArguments, checkSourceElement); diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), node.typeArguments!, Diagnostics.Expected_0_type_arguments_but_got_1, 0, length(node.typeArguments))); } return fakeSignature; } const exprTypes = checkExpression(node.tagName); const apparentType = getApparentType(exprTypes); if (apparentType === errorType) { return resolveErrorCall(node); } const signatures = getUninstantiatedJsxSignaturesOfType(exprTypes, node); if (isUntypedFunctionCall(exprTypes, apparentType, signatures.length, /*constructSignatures*/ 0)) { return resolveUntypedCall(node); } if (signatures.length === 0) { // We found no signatures at all, which is an error error(node.tagName, Diagnostics.JSX_element_type_0_does_not_have_any_construct_or_call_signatures, getTextOfNode(node.tagName)); return resolveErrorCall(node); } return resolveCall(node, signatures, candidatesOutArray, checkMode, SignatureFlags.None); } /** * Sometimes, we have a decorator that could accept zero arguments, * but is receiving too many arguments as part of the decorator invocation. * In those cases, a user may have meant to *call* the expression before using it as a decorator. */ function isPotentiallyUncalledDecorator(decorator: Decorator, signatures: readonly Signature[]) { return signatures.length && every(signatures, signature => signature.minArgumentCount === 0 && !signatureHasRestParameter(signature) && signature.parameters.length < getDecoratorArgumentCount(decorator, signature)); } function resolveSignature(node: CallLikeExpression, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature { switch (node.kind) { case SyntaxKind.CallExpression: return resolveCallExpression(node, candidatesOutArray, checkMode); case SyntaxKind.NewExpression: return resolveNewExpression(node, candidatesOutArray, checkMode); case SyntaxKind.TaggedTemplateExpression: return resolveTaggedTemplateExpression(node, candidatesOutArray, checkMode); case SyntaxKind.Decorator: return resolveDecorator(node, candidatesOutArray, checkMode); case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxSelfClosingElement: return resolveJsxOpeningLikeElement(node, candidatesOutArray, checkMode); } throw Debug.assertNever(node, "Branch in 'resolveSignature' should be unreachable."); } /** * Resolve a signature of a given call-like expression. * @param node a call-like expression to try resolve a signature for * @param candidatesOutArray an array of signature to be filled in by the function. It is passed by signature help in the language service; * the function will fill it up with appropriate candidate signatures * @return a signature of the call-like expression or undefined if one can't be found */ function getResolvedSignature(node: CallLikeExpression, candidatesOutArray?: Signature[] | undefined, checkMode?: CheckMode): Signature { const links = getNodeLinks(node); // If getResolvedSignature has already been called, we will have cached the resolvedSignature. // However, it is possible that either candidatesOutArray was not passed in the first time, // or that a different candidatesOutArray was passed in. Therefore, we need to redo the work // to correctly fill the candidatesOutArray. const cached = links.resolvedSignature; if (cached && cached !== resolvingSignature && !candidatesOutArray) { return cached; } links.resolvedSignature = resolvingSignature; const result = resolveSignature(node, candidatesOutArray, checkMode || CheckMode.Normal); // When CheckMode.SkipGenericFunctions is set we use resolvingSignature to indicate that call // resolution should be deferred. if (result !== resolvingSignature) { // If signature resolution originated in control flow type analysis (for example to compute the // assigned type in a flow assignment) we don't cache the result as it may be based on temporary // types from the control flow analysis. links.resolvedSignature = flowLoopStart === flowLoopCount ? result : cached; } return result; } /** * Indicates whether a declaration can be treated as a constructor in a JavaScript * file. */ function isJSConstructor(node: Node | undefined): node is FunctionDeclaration | FunctionExpression { if (!node || !isInJSFile(node)) { return false; } const func = isFunctionDeclaration(node) || isFunctionExpression(node) ? node : isVariableDeclaration(node) && node.initializer && isFunctionExpression(node.initializer) ? node.initializer : undefined; if (func) { // If the node has a @class tag, treat it like a constructor. if (getJSDocClassTag(node)) return true; // If the symbol of the node has members, treat it like a constructor. const symbol = getSymbolOfNode(func); return !!symbol?.members?.size; } return false; } function mergeJSSymbols(target: Symbol, source: Symbol | undefined) { if (source) { const links = getSymbolLinks(source); if (!links.inferredClassSymbol || !links.inferredClassSymbol.has(getSymbolId(target))) { const inferred = isTransientSymbol(target) ? target : cloneSymbol(target) as TransientSymbol; inferred.exports = inferred.exports || createSymbolTable(); inferred.members = inferred.members || createSymbolTable(); inferred.flags |= source.flags & SymbolFlags.Class; if (source.exports?.size) { mergeSymbolTable(inferred.exports, source.exports); } if (source.members?.size) { mergeSymbolTable(inferred.members, source.members); } (links.inferredClassSymbol || (links.inferredClassSymbol = new Map())).set(getSymbolId(inferred), inferred); return inferred; } return links.inferredClassSymbol.get(getSymbolId(target)); } } function getAssignedClassSymbol(decl: Declaration): Symbol | undefined { const assignmentSymbol = decl && getSymbolOfExpando(decl, /*allowDeclaration*/ true); const prototype = assignmentSymbol?.exports?.get("prototype" as __String); const init = prototype?.valueDeclaration && getAssignedJSPrototype(prototype.valueDeclaration); return init ? getSymbolOfNode(init) : undefined; } function getSymbolOfExpando(node: Node, allowDeclaration: boolean): Symbol | undefined { if (!node.parent) { return undefined; } let name: Expression | BindingName | undefined; let decl: Node | undefined; if (isVariableDeclaration(node.parent) && node.parent.initializer === node) { if (!isInJSFile(node) && !(isVarConst(node.parent) && isFunctionLikeDeclaration(node))) { return undefined; } name = node.parent.name; decl = node.parent; } else if (isBinaryExpression(node.parent)) { const parentNode = node.parent; const parentNodeOperator = node.parent.operatorToken.kind; if (parentNodeOperator === SyntaxKind.EqualsToken && (allowDeclaration || parentNode.right === node)) { name = parentNode.left; decl = name; } else if (parentNodeOperator === SyntaxKind.BarBarToken || parentNodeOperator === SyntaxKind.QuestionQuestionToken) { if (isVariableDeclaration(parentNode.parent) && parentNode.parent.initializer === parentNode) { name = parentNode.parent.name; decl = parentNode.parent; } else if (isBinaryExpression(parentNode.parent) && parentNode.parent.operatorToken.kind === SyntaxKind.EqualsToken && (allowDeclaration || parentNode.parent.right === parentNode)) { name = parentNode.parent.left; decl = name; } if (!name || !isBindableStaticNameExpression(name) || !isSameEntityName(name, parentNode.left)) { return undefined; } } } else if (allowDeclaration && isFunctionDeclaration(node)) { name = node.name; decl = node; } if (!decl || !name || (!allowDeclaration && !getExpandoInitializer(node, isPrototypeAccess(name)))) { return undefined; } return getSymbolOfNode(decl); } function getAssignedJSPrototype(node: Node) { if (!node.parent) { return false; } let parent: Node = node.parent; while (parent && parent.kind === SyntaxKind.PropertyAccessExpression) { parent = parent.parent; } if (parent && isBinaryExpression(parent) && isPrototypeAccess(parent.left) && parent.operatorToken.kind === SyntaxKind.EqualsToken) { const right = getInitializerOfBinaryExpression(parent); return isObjectLiteralExpression(right) && right; } } /** * Syntactically and semantically checks a call or new expression. * @param node The call/new expression to be checked. * @returns On success, the expression's signature's return type. On failure, anyType. */ function checkCallExpression(node: CallExpression | NewExpression, checkMode?: CheckMode): Type { if (!checkGrammarTypeArguments(node, node.typeArguments)) checkGrammarArguments(node.arguments); const signature = getResolvedSignature(node, /*candidatesOutArray*/ undefined, checkMode); if (signature === resolvingSignature) { // CheckMode.SkipGenericFunctions is enabled and this is a call to a generic function that // returns a function type. We defer checking and return nonInferrableType. return nonInferrableType; } checkDeprecatedSignature(signature, node); if (node.expression.kind === SyntaxKind.SuperKeyword) { return voidType; } if (node.kind === SyntaxKind.NewExpression) { const declaration = signature.declaration; if (declaration && declaration.kind !== SyntaxKind.Constructor && declaration.kind !== SyntaxKind.ConstructSignature && declaration.kind !== SyntaxKind.ConstructorType && !isJSDocConstructSignature(declaration) && !isJSConstructor(declaration)) { // When resolved signature is a call signature (and not a construct signature) the result type is any if (noImplicitAny) { error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type); } return anyType; } } // In JavaScript files, calls to any identifier 'require' are treated as external module imports if (isInJSFile(node) && isCommonJsRequire(node)) { return resolveExternalModuleTypeByLiteral(node.arguments![0] as StringLiteral); } const returnType = getReturnTypeOfSignature(signature); // Treat any call to the global 'Symbol' function that is part of a const variable or readonly property // as a fresh unique symbol literal type. if (returnType.flags & TypeFlags.ESSymbolLike && isSymbolOrSymbolForCall(node)) { return getESSymbolLikeTypeForNode(walkUpParenthesizedExpressions(node.parent)); } if (node.kind === SyntaxKind.CallExpression && node.parent.kind === SyntaxKind.ExpressionStatement && returnType.flags & TypeFlags.Void && getTypePredicateOfSignature(signature)) { if (!isDottedName(node.expression)) { error(node.expression, Diagnostics.Assertions_require_the_call_target_to_be_an_identifier_or_qualified_name); } else if (!getEffectsSignature(node)) { const diagnostic = error(node.expression, Diagnostics.Assertions_require_every_name_in_the_call_target_to_be_declared_with_an_explicit_type_annotation); getTypeOfDottedName(node.expression, diagnostic); } } if (isInJSFile(node)) { const jsSymbol = getSymbolOfExpando(node, /*allowDeclaration*/ false); if (jsSymbol?.exports?.size) { const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, undefined, undefined); jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral; return getIntersectionType([returnType, jsAssignmentType]); } } return returnType; } function checkDeprecatedSignature(signature: Signature, node: CallLikeExpression) { if (signature.declaration && signature.declaration.flags & NodeFlags.Deprecated) { const suggestionNode = getDeprecatedSuggestionNode(node); errorOrSuggestion(/*isError*/ false, suggestionNode, Diagnostics._0_is_deprecated, signatureToString(signature)); } } function getDeprecatedSuggestionNode(node: Node): Node { node = skipParentheses(node); switch (node.kind) { case SyntaxKind.CallExpression: case SyntaxKind.Decorator: case SyntaxKind.NewExpression: return getDeprecatedSuggestionNode((node).expression); case SyntaxKind.TaggedTemplateExpression: return getDeprecatedSuggestionNode((node).tag); case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxSelfClosingElement: return getDeprecatedSuggestionNode((node).tagName); case SyntaxKind.ElementAccessExpression: return (node).argumentExpression; case SyntaxKind.PropertyAccessExpression: return (node).name; case SyntaxKind.TypeReference: const typeReference = node; return isQualifiedName(typeReference.typeName) ? typeReference.typeName.right : typeReference; default: return node; } } function isSymbolOrSymbolForCall(node: Node) { if (!isCallExpression(node)) return false; let left = node.expression; if (isPropertyAccessExpression(left) && left.name.escapedText === "for") { left = left.expression; } if (!isIdentifier(left) || left.escapedText !== "Symbol") { return false; } // make sure `Symbol` is the global symbol const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); if (!globalESSymbol) { return false; } return globalESSymbol === resolveName(left, "Symbol" as __String, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); } function checkImportCallExpression(node: ImportCall): Type { // Check grammar of dynamic import if (!checkGrammarArguments(node.arguments)) checkGrammarImportCallExpression(node); if (node.arguments.length === 0) { return createPromiseReturnType(node, anyType); } const specifier = node.arguments[0]; const specifierType = checkExpressionCached(specifier); // Even though multiple arguments is grammatically incorrect, type-check extra arguments for completion for (let i = 1; i < node.arguments.length; ++i) { checkExpressionCached(node.arguments[i]); } if (specifierType.flags & TypeFlags.Undefined || specifierType.flags & TypeFlags.Null || !isTypeAssignableTo(specifierType, stringType)) { error(specifier, Diagnostics.Dynamic_import_s_specifier_must_be_of_type_string_but_here_has_type_0, typeToString(specifierType)); } // resolveExternalModuleName will return undefined if the moduleReferenceExpression is not a string literal const moduleSymbol = resolveExternalModuleName(node, specifier); if (moduleSymbol) { const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontRecursivelyResolve*/ true, /*suppressUsageError*/ false); if (esModuleSymbol) { return createPromiseReturnType(node, getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol)); } } return createPromiseReturnType(node, anyType); } function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol, originalSymbol: Symbol): Type { if (allowSyntheticDefaultImports && type && type !== errorType) { const synthType = type as SyntheticDefaultModuleType; if (!synthType.syntheticType) { const file = find(originalSymbol.declarations, isSourceFile); const hasSyntheticDefault = canHaveSyntheticDefault(file, originalSymbol, /*dontResolveAlias*/ false); if (hasSyntheticDefault) { const memberTable = createSymbolTable(); const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); newSymbol.parent = originalSymbol; newSymbol.nameType = getLiteralType("default"); newSymbol.target = resolveSymbol(symbol); memberTable.set(InternalSymbolName.Default, newSymbol); const anonymousSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); const defaultContainingObject = createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined); anonymousSymbol.type = defaultContainingObject; synthType.syntheticType = isValidSpreadType(type) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*objectFlags*/ 0, /*readonly*/ false) : defaultContainingObject; } else { synthType.syntheticType = type; } } return synthType.syntheticType; } return type; } function isCommonJsRequire(node: Node): boolean { if (!isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { return false; } // Make sure require is not a local function if (!isIdentifier(node.expression)) return Debug.fail(); const resolvedRequire = resolveName(node.expression, node.expression.escapedText, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true)!; // TODO: GH#18217 if (resolvedRequire === requireSymbol) { return true; } // project includes symbol named 'require' - make sure that it is ambient and local non-alias if (resolvedRequire.flags & SymbolFlags.Alias) { return false; } const targetDeclarationKind = resolvedRequire.flags & SymbolFlags.Function ? SyntaxKind.FunctionDeclaration : resolvedRequire.flags & SymbolFlags.Variable ? SyntaxKind.VariableDeclaration : SyntaxKind.Unknown; if (targetDeclarationKind !== SyntaxKind.Unknown) { const decl = getDeclarationOfKind(resolvedRequire, targetDeclarationKind)!; // function/variable declaration should be ambient return !!decl && !!(decl.flags & NodeFlags.Ambient); } return false; } function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type { if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments); if (languageVersion < ScriptTarget.ES2015) { checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject); } const signature = getResolvedSignature(node); checkDeprecatedSignature(signature, node); return getReturnTypeOfSignature(signature); } function checkAssertion(node: AssertionExpression) { return checkAssertionWorker(node, node.type, node.expression); } function isValidConstAssertionArgument(node: Node): boolean { switch (node.kind) { case SyntaxKind.StringLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.NumericLiteral: case SyntaxKind.BigIntLiteral: case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.TemplateExpression: return true; case SyntaxKind.ParenthesizedExpression: return isValidConstAssertionArgument((node).expression); case SyntaxKind.PrefixUnaryExpression: const op = (node).operator; const arg = (node).operand; return op === SyntaxKind.MinusToken && (arg.kind === SyntaxKind.NumericLiteral || arg.kind === SyntaxKind.BigIntLiteral) || op === SyntaxKind.PlusToken && arg.kind === SyntaxKind.NumericLiteral; case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: const expr = (node).expression; if (isIdentifier(expr)) { let symbol = getSymbolAtLocation(expr); if (symbol && symbol.flags & SymbolFlags.Alias) { symbol = resolveAlias(symbol); } return !!(symbol && (symbol.flags & SymbolFlags.Enum) && getEnumKind(symbol) === EnumKind.Literal); } } return false; } function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) { let exprType = checkExpression(expression, checkMode); if (isConstTypeReference(type)) { if (!isValidConstAssertionArgument(expression)) { error(expression, Diagnostics.A_const_assertions_can_only_be_applied_to_references_to_enum_members_or_string_number_boolean_array_or_object_literals); } return getRegularTypeOfLiteralType(exprType); } checkSourceElement(type); exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(exprType)); const targetType = getTypeFromTypeNode(type); if (produceDiagnostics && targetType !== errorType) { const widenedType = getWidenedType(exprType); if (!isTypeComparableTo(targetType, widenedType)) { checkTypeComparableTo(exprType, targetType, errNode, Diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first); } } return targetType; } function checkNonNullChain(node: NonNullChain) { const leftType = checkExpression(node.expression); const nonOptionalType = getOptionalExpressionType(leftType, node.expression); return propagateOptionalTypeMarker(getNonNullableType(nonOptionalType), node, nonOptionalType !== leftType); } function checkNonNullAssertion(node: NonNullExpression) { return node.flags & NodeFlags.OptionalChain ? checkNonNullChain(node as NonNullChain) : getNonNullableType(checkExpression(node.expression)); } function checkMetaProperty(node: MetaProperty): Type { checkGrammarMetaProperty(node); if (node.keywordToken === SyntaxKind.NewKeyword) { return checkNewTargetMetaProperty(node); } if (node.keywordToken === SyntaxKind.ImportKeyword) { return checkImportMetaProperty(node); } return Debug.assertNever(node.keywordToken); } function checkNewTargetMetaProperty(node: MetaProperty) { const container = getNewTargetContainer(node); if (!container) { error(node, Diagnostics.Meta_property_0_is_only_allowed_in_the_body_of_a_function_declaration_function_expression_or_constructor, "new.target"); return errorType; } else if (container.kind === SyntaxKind.Constructor) { const symbol = getSymbolOfNode(container.parent as ClassLikeDeclaration); return getTypeOfSymbol(symbol); } else { const symbol = getSymbolOfNode(container)!; return getTypeOfSymbol(symbol); } } function checkImportMetaProperty(node: MetaProperty) { if (moduleKind !== ModuleKind.ES2020 && moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System) { error(node, Diagnostics.The_import_meta_meta_property_is_only_allowed_when_the_module_option_is_es2020_esnext_or_system); } const file = getSourceFileOfNode(node); Debug.assert(!!(file.flags & NodeFlags.PossiblyContainsImportMeta), "Containing file is missing import meta node flag."); Debug.assert(!!file.externalModuleIndicator, "Containing file should be a module."); return node.name.escapedText === "meta" ? getGlobalImportMetaType() : errorType; } function getTypeOfParameter(symbol: Symbol) { const type = getTypeOfSymbol(symbol); if (strictNullChecks) { const declaration = symbol.valueDeclaration; if (declaration && hasInitializer(declaration)) { return getOptionalType(type); } } return type; } function getTupleElementLabel(d: ParameterDeclaration | NamedTupleMember) { Debug.assert(isIdentifier(d.name)); // Parameter declarations could be binding patterns, but we only allow identifier names return d.name.escapedText; } function getParameterNameAtPosition(signature: Signature, pos: number, overrideRestType?: Type) { const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); if (pos < paramCount) { return signature.parameters[pos].escapedName; } const restParameter = signature.parameters[paramCount] || unknownSymbol; const restType = overrideRestType || getTypeOfSymbol(restParameter); if (isTupleType(restType)) { const associatedNames = ((restType).target).labeledElementDeclarations; const index = pos - paramCount; return associatedNames && getTupleElementLabel(associatedNames[index]) || restParameter.escapedName + "_" + index as __String; } return restParameter.escapedName; } function isValidDeclarationForTupleLabel(d: Declaration): d is NamedTupleMember | (ParameterDeclaration & { name: Identifier }) { return d.kind === SyntaxKind.NamedTupleMember || (isParameter(d) && d.name && isIdentifier(d.name)); } function getNameableDeclarationAtPosition(signature: Signature, pos: number) { const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); if (pos < paramCount) { const decl = signature.parameters[pos].valueDeclaration; return decl && isValidDeclarationForTupleLabel(decl) ? decl : undefined; } const restParameter = signature.parameters[paramCount] || unknownSymbol; const restType = getTypeOfSymbol(restParameter); if (isTupleType(restType)) { const associatedNames = ((restType).target).labeledElementDeclarations; const index = pos - paramCount; return associatedNames && associatedNames[index]; } return restParameter.valueDeclaration && isValidDeclarationForTupleLabel(restParameter.valueDeclaration) ? restParameter.valueDeclaration : undefined; } function getTypeAtPosition(signature: Signature, pos: number): Type { return tryGetTypeAtPosition(signature, pos) || anyType; } function tryGetTypeAtPosition(signature: Signature, pos: number): Type | undefined { const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); if (pos < paramCount) { return getTypeOfParameter(signature.parameters[pos]); } if (signatureHasRestParameter(signature)) { // We want to return the value undefined for an out of bounds parameter position, // so we need to check bounds here before calling getIndexedAccessType (which // otherwise would return the type 'undefined'). const restType = getTypeOfSymbol(signature.parameters[paramCount]); const index = pos - paramCount; if (!isTupleType(restType) || restType.target.hasRestElement || index < restType.target.fixedLength) { return getIndexedAccessType(restType, getLiteralType(index)); } } return undefined; } function getRestTypeAtPosition(source: Signature, pos: number): Type { const parameterCount = getParameterCount(source); const minArgumentCount = getMinArgumentCount(source); const restType = getEffectiveRestType(source); if (restType && pos >= parameterCount - 1) { return pos === parameterCount - 1 ? restType : createArrayType(getIndexedAccessType(restType, numberType)); } const types = []; const flags = []; const names = []; for (let i = pos; i < parameterCount; i++) { if (!restType || i < parameterCount - 1) { types.push(getTypeAtPosition(source, i)); flags.push(i < minArgumentCount ? ElementFlags.Required : ElementFlags.Optional); } else { types.push(restType); flags.push(ElementFlags.Variadic); } const name = getNameableDeclarationAtPosition(source, i); if (name) { names.push(name); } } return createTupleType(types, flags, /*readonly*/ false, length(names) === length(types) ? names : undefined); } function getParameterCount(signature: Signature) { const length = signature.parameters.length; if (signatureHasRestParameter(signature)) { const restType = getTypeOfSymbol(signature.parameters[length - 1]); if (isTupleType(restType)) { return length + restType.target.fixedLength - (restType.target.hasRestElement ? 0 : 1); } } return length; } function getMinArgumentCount(signature: Signature, flags?: MinArgumentCountFlags) { const strongArityForUntypedJS = flags! & MinArgumentCountFlags.StrongArityForUntypedJS; const voidIsNonOptional = flags! & MinArgumentCountFlags.VoidIsNonOptional; if (voidIsNonOptional || signature.resolvedMinArgumentCount === undefined) { let minArgumentCount: number | undefined; if (signatureHasRestParameter(signature)) { const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); if (isTupleType(restType)) { const firstOptionalIndex = findIndex(restType.target.elementFlags, f => !(f & ElementFlags.Required)); const requiredCount = firstOptionalIndex < 0 ? restType.target.fixedLength : firstOptionalIndex; if (requiredCount > 0) { minArgumentCount = signature.parameters.length - 1 + requiredCount; } } } if (minArgumentCount === undefined) { if (!strongArityForUntypedJS && signature.flags & SignatureFlags.IsUntypedSignatureInJSFile) { return 0; } minArgumentCount = signature.minArgumentCount; } if (voidIsNonOptional) { return minArgumentCount; } for (let i = minArgumentCount - 1; i >= 0; i--) { const type = getTypeAtPosition(signature, i); if (filterType(type, acceptsVoid).flags & TypeFlags.Never) { break; } minArgumentCount = i; } signature.resolvedMinArgumentCount = minArgumentCount; } return signature.resolvedMinArgumentCount; } function hasEffectiveRestParameter(signature: Signature) { if (signatureHasRestParameter(signature)) { const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); return !isTupleType(restType) || restType.target.hasRestElement; } return false; } function getEffectiveRestType(signature: Signature) { if (signatureHasRestParameter(signature)) { const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); if (!isTupleType(restType)) { return restType; } if (restType.target.hasRestElement) { return sliceTupleType(restType, restType.target.fixedLength); } } return undefined; } function getNonArrayRestType(signature: Signature) { const restType = getEffectiveRestType(signature); return restType && !isArrayType(restType) && !isTypeAny(restType) && (getReducedType(restType).flags & TypeFlags.Never) === 0 ? restType : undefined; } function getTypeOfFirstParameterOfSignature(signature: Signature) { return getTypeOfFirstParameterOfSignatureWithFallback(signature, neverType); } function getTypeOfFirstParameterOfSignatureWithFallback(signature: Signature, fallbackType: Type) { return signature.parameters.length > 0 ? getTypeAtPosition(signature, 0) : fallbackType; } function inferFromAnnotatedParameters(signature: Signature, context: Signature, inferenceContext: InferenceContext) { const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); for (let i = 0; i < len; i++) { const declaration = signature.parameters[i].valueDeclaration; if (declaration.type) { const typeNode = getEffectiveTypeAnnotationNode(declaration); if (typeNode) { inferTypes(inferenceContext.inferences, getTypeFromTypeNode(typeNode), getTypeAtPosition(context, i)); } } } const restType = getEffectiveRestType(context); if (restType && restType.flags & TypeFlags.TypeParameter) { // The contextual signature has a generic rest parameter. We first instantiate the contextual // signature (without fixing type parameters) and assign types to contextually typed parameters. const instantiatedContext = instantiateSignature(context, inferenceContext.nonFixingMapper); assignContextualParameterTypes(signature, instantiatedContext); // We then infer from a tuple type representing the parameters that correspond to the contextual // rest parameter. const restPos = getParameterCount(context) - 1; inferTypes(inferenceContext.inferences, getRestTypeAtPosition(signature, restPos), restType); } } function assignContextualParameterTypes(signature: Signature, context: Signature) { signature.typeParameters = context.typeParameters; if (context.thisParameter) { const parameter = signature.thisParameter; if (!parameter || parameter.valueDeclaration && !(parameter.valueDeclaration).type) { if (!parameter) { signature.thisParameter = createSymbolWithType(context.thisParameter, /*type*/ undefined); } assignParameterType(signature.thisParameter!, getTypeOfSymbol(context.thisParameter)); } } const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); for (let i = 0; i < len; i++) { const parameter = signature.parameters[i]; if (!getEffectiveTypeAnnotationNode(parameter.valueDeclaration)) { const contextualParameterType = tryGetTypeAtPosition(context, i); assignParameterType(parameter, contextualParameterType); } } if (signatureHasRestParameter(signature)) { // parameter might be a transient symbol generated by use of `arguments` in the function body. const parameter = last(signature.parameters); if (isTransientSymbol(parameter) || !getEffectiveTypeAnnotationNode(parameter.valueDeclaration)) { const contextualParameterType = getRestTypeAtPosition(context, len); assignParameterType(parameter, contextualParameterType); } } } function assignNonContextualParameterTypes(signature: Signature) { if (signature.thisParameter) { assignParameterType(signature.thisParameter); } for (const parameter of signature.parameters) { assignParameterType(parameter); } } function assignParameterType(parameter: Symbol, type?: Type) { const links = getSymbolLinks(parameter); if (!links.type) { const declaration = parameter.valueDeclaration as ParameterDeclaration; links.type = type || getWidenedTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true); if (declaration.name.kind !== SyntaxKind.Identifier) { // if inference didn't come up with anything but unknown, fall back to the binding pattern if present. if (links.type === unknownType) { links.type = getTypeFromBindingPattern(declaration.name); } assignBindingElementTypes(declaration.name); } } } // When contextual typing assigns a type to a parameter that contains a binding pattern, we also need to push // the destructured type into the contained binding elements. function assignBindingElementTypes(pattern: BindingPattern) { for (const element of pattern.elements) { if (!isOmittedExpression(element)) { if (element.name.kind === SyntaxKind.Identifier) { getSymbolLinks(getSymbolOfNode(element)).type = getTypeForBindingElement(element); } else { assignBindingElementTypes(element.name); } } } } function createPromiseType(promisedType: Type): Type { // creates a `Promise` type where `T` is the promisedType argument const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); if (globalPromiseType !== emptyGenericType) { // if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type promisedType = getAwaitedType(promisedType) || unknownType; return createTypeReference(globalPromiseType, [promisedType]); } return unknownType; } function createPromiseLikeType(promisedType: Type): Type { // creates a `PromiseLike` type where `T` is the promisedType argument const globalPromiseLikeType = getGlobalPromiseLikeType(/*reportErrors*/ true); if (globalPromiseLikeType !== emptyGenericType) { // if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type promisedType = getAwaitedType(promisedType) || unknownType; return createTypeReference(globalPromiseLikeType, [promisedType]); } return unknownType; } function createPromiseReturnType(func: FunctionLikeDeclaration | ImportCall, promisedType: Type) { const promiseType = createPromiseType(promisedType); if (promiseType === unknownType) { error(func, isImportCall(func) ? Diagnostics.A_dynamic_import_call_returns_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option : Diagnostics.An_async_function_or_method_must_return_a_Promise_Make_sure_you_have_a_declaration_for_Promise_or_include_ES2015_in_your_lib_option); return errorType; } else if (!getGlobalPromiseConstructorSymbol(/*reportErrors*/ true)) { error(func, isImportCall(func) ? Diagnostics.A_dynamic_import_call_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option : Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); } return promiseType; } function getReturnTypeFromBody(func: FunctionLikeDeclaration, checkMode?: CheckMode): Type { if (!func.body) { return errorType; } const functionFlags = getFunctionFlags(func); const isAsync = (functionFlags & FunctionFlags.Async) !== 0; const isGenerator = (functionFlags & FunctionFlags.Generator) !== 0; let returnType: Type | undefined; let yieldType: Type | undefined; let nextType: Type | undefined; let fallbackReturnType: Type = voidType; if (func.body.kind !== SyntaxKind.Block) { // Async or normal arrow function returnType = checkExpressionCached(func.body, checkMode && checkMode & ~CheckMode.SkipGenericFunctions); if (isAsync) { // From within an async function you can return either a non-promise value or a promise. Any // Promise/A+ compatible implementation will always assimilate any foreign promise, so the // return type of the body should be unwrapped to its awaited type, which we will wrap in // the native Promise type later in this function. returnType = checkAwaitedType(returnType, /*errorNode*/ func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); } } else if (isGenerator) { // Generator or AsyncGenerator function const returnTypes = checkAndAggregateReturnExpressionTypes(func, checkMode); if (!returnTypes) { fallbackReturnType = neverType; } else if (returnTypes.length > 0) { returnType = getUnionType(returnTypes, UnionReduction.Subtype); } const { yieldTypes, nextTypes } = checkAndAggregateYieldOperandTypes(func, checkMode); yieldType = some(yieldTypes) ? getUnionType(yieldTypes, UnionReduction.Subtype) : undefined; nextType = some(nextTypes) ? getIntersectionType(nextTypes) : undefined; } else { // Async or normal function const types = checkAndAggregateReturnExpressionTypes(func, checkMode); if (!types) { // For an async function, the return type will not be never, but rather a Promise for never. return functionFlags & FunctionFlags.Async ? createPromiseReturnType(func, neverType) // Async function : neverType; // Normal function } if (types.length === 0) { // For an async function, the return type will not be void, but rather a Promise for void. return functionFlags & FunctionFlags.Async ? createPromiseReturnType(func, voidType) // Async function : voidType; // Normal function } // Return a union of the return expression types. returnType = getUnionType(types, UnionReduction.Subtype); } if (returnType || yieldType || nextType) { if (yieldType) reportErrorsFromWidening(func, yieldType, WideningKind.GeneratorYield); if (returnType) reportErrorsFromWidening(func, returnType, WideningKind.FunctionReturn); if (nextType) reportErrorsFromWidening(func, nextType, WideningKind.GeneratorNext); if (returnType && isUnitType(returnType) || yieldType && isUnitType(yieldType) || nextType && isUnitType(nextType)) { const contextualSignature = getContextualSignatureForFunctionLikeDeclaration(func); const contextualType = !contextualSignature ? undefined : contextualSignature === getSignatureFromDeclaration(func) ? isGenerator ? undefined : returnType : instantiateContextualType(getReturnTypeOfSignature(contextualSignature), func); if (isGenerator) { yieldType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(yieldType, contextualType, IterationTypeKind.Yield, isAsync); returnType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(returnType, contextualType, IterationTypeKind.Return, isAsync); nextType = getWidenedLiteralLikeTypeForContextualIterationTypeIfNeeded(nextType, contextualType, IterationTypeKind.Next, isAsync); } else { returnType = getWidenedLiteralLikeTypeForContextualReturnTypeIfNeeded(returnType, contextualType, isAsync); } } if (yieldType) yieldType = getWidenedType(yieldType); if (returnType) returnType = getWidenedType(returnType); if (nextType) nextType = getWidenedType(nextType); } if (isGenerator) { return createGeneratorReturnType( yieldType || neverType, returnType || fallbackReturnType, nextType || getContextualIterationType(IterationTypeKind.Next, func) || unknownType, isAsync); } else { // From within an async function you can return either a non-promise value or a promise. Any // Promise/A+ compatible implementation will always assimilate any foreign promise, so the // return type of the body is awaited type of the body, wrapped in a native Promise type. return isAsync ? createPromiseType(returnType || fallbackReturnType) : returnType || fallbackReturnType; } } function createGeneratorReturnType(yieldType: Type, returnType: Type, nextType: Type, isAsyncGenerator: boolean) { const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver; const globalGeneratorType = resolver.getGlobalGeneratorType(/*reportErrors*/ false); yieldType = resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || unknownType; returnType = resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || unknownType; nextType = resolver.resolveIterationType(nextType, /*errorNode*/ undefined) || unknownType; if (globalGeneratorType === emptyGenericType) { // Fall back to the global IterableIterator if returnType is assignable to the expected return iteration // type of IterableIterator, and the expected next iteration type of IterableIterator is assignable to // nextType. const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false); const iterationTypes = globalType !== emptyGenericType ? getIterationTypesOfGlobalIterableType(globalType, resolver) : undefined; const iterableIteratorReturnType = iterationTypes ? iterationTypes.returnType : anyType; const iterableIteratorNextType = iterationTypes ? iterationTypes.nextType : undefinedType; if (isTypeAssignableTo(returnType, iterableIteratorReturnType) && isTypeAssignableTo(iterableIteratorNextType, nextType)) { if (globalType !== emptyGenericType) { return createTypeFromGenericGlobalType(globalType, [yieldType]); } // The global IterableIterator type doesn't exist, so report an error resolver.getGlobalIterableIteratorType(/*reportErrors*/ true); return emptyObjectType; } // The global Generator type doesn't exist, so report an error resolver.getGlobalGeneratorType(/*reportErrors*/ true); return emptyObjectType; } return createTypeFromGenericGlobalType(globalGeneratorType, [yieldType, returnType, nextType]); } function checkAndAggregateYieldOperandTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined) { const yieldTypes: Type[] = []; const nextTypes: Type[] = []; const isAsync = (getFunctionFlags(func) & FunctionFlags.Async) !== 0; forEachYieldExpression(func.body, yieldExpression => { const yieldExpressionType = yieldExpression.expression ? checkExpression(yieldExpression.expression, checkMode) : undefinedWideningType; pushIfUnique(yieldTypes, getYieldedTypeOfYieldExpression(yieldExpression, yieldExpressionType, anyType, isAsync)); let nextType: Type | undefined; if (yieldExpression.asteriskToken) { const iterationTypes = getIterationTypesOfIterable( yieldExpressionType, isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar, yieldExpression.expression); nextType = iterationTypes && iterationTypes.nextType; } else { nextType = getContextualType(yieldExpression); } if (nextType) pushIfUnique(nextTypes, nextType); }); return { yieldTypes, nextTypes }; } function getYieldedTypeOfYieldExpression(node: YieldExpression, expressionType: Type, sentType: Type, isAsync: boolean): Type | undefined { const errorNode = node.expression || node; // A `yield*` expression effectively yields everything that its operand yields const yieldedType = node.asteriskToken ? checkIteratedTypeOrElementType(isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar, expressionType, sentType, errorNode) : expressionType; return !isAsync ? yieldedType : getAwaitedType(yieldedType, errorNode, node.asteriskToken ? Diagnostics.Type_of_iterated_elements_of_a_yield_Asterisk_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member : Diagnostics.Type_of_yield_operand_in_an_async_generator_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); } /** * Collect the TypeFacts learned from a typeof switch with * total clauses `witnesses`, and the active clause ranging * from `start` to `end`. Parameter `hasDefault` denotes * whether the active clause contains a default clause. */ function getFactsFromTypeofSwitch(start: number, end: number, witnesses: string[], hasDefault: boolean): TypeFacts { let facts: TypeFacts = TypeFacts.None; // When in the default we only collect inequality facts // because default is 'in theory' a set of infinite // equalities. if (hasDefault) { // Value is not equal to any types after the active clause. for (let i = end; i < witnesses.length; i++) { facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject; } // Remove inequalities for types that appear in the // active clause because they appear before other // types collected so far. for (let i = start; i < end; i++) { facts &= ~(typeofNEFacts.get(witnesses[i]) || 0); } // Add inequalities for types before the active clause unconditionally. for (let i = 0; i < start; i++) { facts |= typeofNEFacts.get(witnesses[i]) || TypeFacts.TypeofNEHostObject; } } // When in an active clause without default the set of // equalities is finite. else { // Add equalities for all types in the active clause. for (let i = start; i < end; i++) { facts |= typeofEQFacts.get(witnesses[i]) || TypeFacts.TypeofEQHostObject; } // Remove equalities for types that appear before the // active clause. for (let i = 0; i < start; i++) { facts &= ~(typeofEQFacts.get(witnesses[i]) || 0); } } return facts; } function isExhaustiveSwitchStatement(node: SwitchStatement): boolean { const links = getNodeLinks(node); return links.isExhaustive !== undefined ? links.isExhaustive : (links.isExhaustive = computeExhaustiveSwitchStatement(node)); } function computeExhaustiveSwitchStatement(node: SwitchStatement): boolean { if (node.expression.kind === SyntaxKind.TypeOfExpression) { const operandType = getTypeOfExpression((node.expression as TypeOfExpression).expression); const witnesses = getSwitchClauseTypeOfWitnesses(node, /*retainDefault*/ false); // notEqualFacts states that the type of the switched value is not equal to every type in the switch. const notEqualFacts = getFactsFromTypeofSwitch(0, 0, witnesses, /*hasDefault*/ true); const type = getBaseConstraintOfType(operandType) || operandType; // Take any/unknown as a special condition. Or maybe we could change `type` to a union containing all primitive types. if (type.flags & TypeFlags.AnyOrUnknown) { return (TypeFacts.AllTypeofNE & notEqualFacts) === TypeFacts.AllTypeofNE; } return !!(filterType(type, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts).flags & TypeFlags.Never); } const type = getTypeOfExpression(node.expression); if (!isLiteralType(type)) { return false; } const switchTypes = getSwitchClauseTypes(node); if (!switchTypes.length || some(switchTypes, isNeitherUnitTypeNorNever)) { return false; } return eachTypeContainedIn(mapType(type, getRegularTypeOfLiteralType), switchTypes); } function functionHasImplicitReturn(func: FunctionLikeDeclaration) { return func.endFlowNode && isReachableFlowNode(func.endFlowNode); } /** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means func returns `void`, `undefined` means it returns `never`. */ function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined): Type[] | undefined { const functionFlags = getFunctionFlags(func); const aggregatedTypes: Type[] = []; let hasReturnWithNoExpression = functionHasImplicitReturn(func); let hasReturnOfTypeNever = false; forEachReturnStatement(func.body, returnStatement => { const expr = returnStatement.expression; if (expr) { let type = checkExpressionCached(expr, checkMode && checkMode & ~CheckMode.SkipGenericFunctions); if (functionFlags & FunctionFlags.Async) { // From within an async function you can return either a non-promise value or a promise. Any // Promise/A+ compatible implementation will always assimilate any foreign promise, so the // return type of the body should be unwrapped to its awaited type, which should be wrapped in // the native Promise type by the caller. type = checkAwaitedType(type, func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); } if (type.flags & TypeFlags.Never) { hasReturnOfTypeNever = true; } pushIfUnique(aggregatedTypes, type); } else { hasReturnWithNoExpression = true; } }); if (aggregatedTypes.length === 0 && !hasReturnWithNoExpression && (hasReturnOfTypeNever || mayReturnNever(func))) { return undefined; } if (strictNullChecks && aggregatedTypes.length && hasReturnWithNoExpression && !(isJSConstructor(func) && aggregatedTypes.some(t => t.symbol === func.symbol))) { // Javascript "callable constructors", containing eg `if (!(this instanceof A)) return new A()` should not add undefined pushIfUnique(aggregatedTypes, undefinedType); } return aggregatedTypes; } function mayReturnNever(func: FunctionLikeDeclaration): boolean { switch (func.kind) { case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: return true; case SyntaxKind.MethodDeclaration: return func.parent.kind === SyntaxKind.ObjectLiteralExpression; default: return false; } } /** * TypeScript Specification 1.0 (6.3) - July 2014 * An explicitly typed function whose return type isn't the Void type, * the Any type, or a union type containing the Void or Any type as a constituent * must have at least one return statement somewhere in its body. * An exception to this rule is if the function implementation consists of a single 'throw' statement. * * @param returnType - return type of the function, can be undefined if return type is not explicitly specified */ function checkAllCodePathsInNonVoidFunctionReturnOrThrow(func: FunctionLikeDeclaration | MethodSignature, returnType: Type | undefined): void { if (!produceDiagnostics) { return; } const functionFlags = getFunctionFlags(func); const type = returnType && unwrapReturnType(returnType, functionFlags); // Functions with with an explicitly specified 'void' or 'any' return type don't need any return expressions. if (type && maybeTypeOfKind(type, TypeFlags.Any | TypeFlags.Void)) { return; } // If all we have is a function signature, or an arrow function with an expression body, then there is nothing to check. // also if HasImplicitReturn flag is not set this means that all codepaths in function body end with return or throw if (func.kind === SyntaxKind.MethodSignature || nodeIsMissing(func.body) || func.body!.kind !== SyntaxKind.Block || !functionHasImplicitReturn(func)) { return; } const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn; if (type && type.flags & TypeFlags.Never) { error(getEffectiveReturnTypeNode(func), Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point); } else if (type && !hasExplicitReturn) { // minimal check: function has syntactic return type annotation and no explicit return statements in the body // this function does not conform to the specification. // NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present error(getEffectiveReturnTypeNode(func), Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); } else if (type && strictNullChecks && !isTypeAssignableTo(undefinedType, type)) { error(getEffectiveReturnTypeNode(func) || func, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined); } else if (compilerOptions.noImplicitReturns) { if (!type) { // If return type annotation is omitted check if function has any explicit return statements. // If it does not have any - its inferred return type is void - don't do any checks. // Otherwise get inferred return type from function body and report error only if it is not void / anytype if (!hasExplicitReturn) { return; } const inferredReturnType = getReturnTypeOfSignature(getSignatureFromDeclaration(func)); if (isUnwrappedReturnTypeVoidOrAny(func, inferredReturnType)) { return; } } error(getEffectiveReturnTypeNode(func) || func, Diagnostics.Not_all_code_paths_return_a_value); } } function checkFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode): Type { Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); checkNodeDeferred(node); // The identityMapper object is used to indicate that function expressions are wildcards if (checkMode && checkMode & CheckMode.SkipContextSensitive && isContextSensitive(node)) { // Skip parameters, return signature with return type that retains noncontextual parts so inferences can still be drawn in an early stage if (!getEffectiveReturnTypeNode(node) && !hasContextSensitiveParameters(node)) { // Return plain anyFunctionType if there is no possibility we'll make inferences from the return type const contextualSignature = getContextualSignature(node); if (contextualSignature && couldContainTypeVariables(getReturnTypeOfSignature(contextualSignature))) { const links = getNodeLinks(node); if (links.contextFreeType) { return links.contextFreeType; } const returnType = getReturnTypeFromBody(node, checkMode); const returnOnlySignature = createSignature(undefined, undefined, undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); const returnOnlyType = createAnonymousType(node.symbol, emptySymbols, [returnOnlySignature], emptyArray, undefined, undefined); returnOnlyType.objectFlags |= ObjectFlags.NonInferrableType; return links.contextFreeType = returnOnlyType; } } return anyFunctionType; } // Grammar checking const hasGrammarError = checkGrammarFunctionLikeDeclaration(node); if (!hasGrammarError && node.kind === SyntaxKind.FunctionExpression) { checkGrammarForGenerator(node); } contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node, checkMode); return getTypeOfSymbol(getSymbolOfNode(node)); } function contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode) { const links = getNodeLinks(node); // Check if function expression is contextually typed and assign parameter types if so. if (!(links.flags & NodeCheckFlags.ContextChecked)) { const contextualSignature = getContextualSignature(node); // If a type check is started at a function expression that is an argument of a function call, obtaining the // contextual type may recursively get back to here during overload resolution of the call. If so, we will have // already assigned contextual types. if (!(links.flags & NodeCheckFlags.ContextChecked)) { links.flags |= NodeCheckFlags.ContextChecked; const signature = firstOrUndefined(getSignaturesOfType(getTypeOfSymbol(getSymbolOfNode(node)), SignatureKind.Call)); if (!signature) { return; } if (isContextSensitive(node)) { if (contextualSignature) { const inferenceContext = getInferenceContext(node); if (checkMode && checkMode & CheckMode.Inferential) { inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); } const instantiatedContextualSignature = inferenceContext ? instantiateSignature(contextualSignature, inferenceContext.mapper) : contextualSignature; assignContextualParameterTypes(signature, instantiatedContextualSignature); } else { // Force resolution of all parameter types such that the absence of a contextual type is consistently reflected. assignNonContextualParameterTypes(signature); } } if (contextualSignature && !getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) { const returnType = getReturnTypeFromBody(node, checkMode); if (!signature.resolvedReturnType) { signature.resolvedReturnType = returnType; } } checkSignatureDeclaration(node); } } } function checkFunctionExpressionOrObjectLiteralMethodDeferred(node: ArrowFunction | FunctionExpression | MethodDeclaration) { Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); const functionFlags = getFunctionFlags(node); const returnType = getReturnTypeFromAnnotation(node); checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); if (node.body) { if (!getEffectiveReturnTypeNode(node)) { // There are some checks that are only performed in getReturnTypeFromBody, that may produce errors // we need. An example is the noImplicitAny errors resulting from widening the return expression // of a function. Because checking of function expression bodies is deferred, there was never an // appropriate time to do this during the main walk of the file (see the comment at the top of // checkFunctionExpressionBodies). So it must be done now. getReturnTypeOfSignature(getSignatureFromDeclaration(node)); } if (node.body.kind === SyntaxKind.Block) { checkSourceElement(node.body); } else { // From within an async function you can return either a non-promise value or a promise. Any // Promise/A+ compatible implementation will always assimilate any foreign promise, so we // should not be checking assignability of a promise to the return type. Instead, we need to // check assignability of the awaited type of the expression body against the promised type of // its return type annotation. const exprType = checkExpression(node.body); const returnOrPromisedType = returnType && unwrapReturnType(returnType, functionFlags); if (returnOrPromisedType) { if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function const awaitedType = checkAwaitedType(exprType, node.body, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); checkTypeAssignableToAndOptionallyElaborate(awaitedType, returnOrPromisedType, node.body, node.body); } else { // Normal function checkTypeAssignableToAndOptionallyElaborate(exprType, returnOrPromisedType, node.body, node.body); } } } } } function checkArithmeticOperandType(operand: Node, type: Type, diagnostic: DiagnosticMessage, isAwaitValid = false): boolean { if (!isTypeAssignableTo(type, numberOrBigIntType)) { const awaitedType = isAwaitValid && getAwaitedTypeOfPromise(type); errorAndMaybeSuggestAwait( operand, !!awaitedType && isTypeAssignableTo(awaitedType, numberOrBigIntType), diagnostic); return false; } return true; } function isReadonlyAssignmentDeclaration(d: Declaration) { if (!isCallExpression(d)) { return false; } if (!isBindableObjectDefinePropertyCall(d)) { return false; } const objectLitType = checkExpressionCached(d.arguments[2]); const valueType = getTypeOfPropertyOfType(objectLitType, "value" as __String); if (valueType) { const writableProp = getPropertyOfType(objectLitType, "writable" as __String); const writableType = writableProp && getTypeOfSymbol(writableProp); if (!writableType || writableType === falseType || writableType === regularFalseType) { return true; } // We include this definition whereupon we walk back and check the type at the declaration because // The usual definition of `Object.defineProperty` will _not_ cause literal types to be preserved in the // argument types, should the type be contextualized by the call itself. if (writableProp && writableProp.valueDeclaration && isPropertyAssignment(writableProp.valueDeclaration)) { const initializer = writableProp.valueDeclaration.initializer; const rawOriginalType = checkExpression(initializer); if (rawOriginalType === falseType || rawOriginalType === regularFalseType) { return true; } } return false; } const setProp = getPropertyOfType(objectLitType, "set" as __String); return !setProp; } function isReadonlySymbol(symbol: Symbol): boolean { // The following symbols are considered read-only: // Properties with a 'readonly' modifier // Variables declared with 'const' // Get accessors without matching set accessors // Enum members // Object.defineProperty assignments with writable false or no setter // Unions and intersections of the above (unions and intersections eagerly set isReadonly on creation) return !!(getCheckFlags(symbol) & CheckFlags.Readonly || symbol.flags & SymbolFlags.Property && getDeclarationModifierFlagsFromSymbol(symbol) & ModifierFlags.Readonly || symbol.flags & SymbolFlags.Variable && getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Const || symbol.flags & SymbolFlags.Accessor && !(symbol.flags & SymbolFlags.SetAccessor) || symbol.flags & SymbolFlags.EnumMember || some(symbol.declarations, isReadonlyAssignmentDeclaration) ); } function isAssignmentToReadonlyEntity(expr: Expression, symbol: Symbol, assignmentKind: AssignmentKind) { if (assignmentKind === AssignmentKind.None) { // no assigment means it doesn't matter whether the entity is readonly return false; } if (isReadonlySymbol(symbol)) { // Allow assignments to readonly properties within constructors of the same class declaration. if (symbol.flags & SymbolFlags.Property && isAccessExpression(expr) && expr.expression.kind === SyntaxKind.ThisKeyword) { // Look for if this is the constructor for the class that `symbol` is a property of. const ctor = getContainingFunction(expr); if (!(ctor && (ctor.kind === SyntaxKind.Constructor || isJSConstructor(ctor)))) { return true; } if (symbol.valueDeclaration) { const isAssignmentDeclaration = isBinaryExpression(symbol.valueDeclaration); const isLocalPropertyDeclaration = ctor.parent === symbol.valueDeclaration.parent; const isLocalParameterProperty = ctor === symbol.valueDeclaration.parent; const isLocalThisPropertyAssignment = isAssignmentDeclaration && symbol.parent?.valueDeclaration === ctor.parent; const isLocalThisPropertyAssignmentConstructorFunction = isAssignmentDeclaration && symbol.parent?.valueDeclaration === ctor; const isWriteableSymbol = isLocalPropertyDeclaration || isLocalParameterProperty || isLocalThisPropertyAssignment || isLocalThisPropertyAssignmentConstructorFunction; return !isWriteableSymbol; } } return true; } if (isAccessExpression(expr)) { // references through namespace import should be readonly const node = skipParentheses(expr.expression); if (node.kind === SyntaxKind.Identifier) { const symbol = getNodeLinks(node).resolvedSymbol!; if (symbol.flags & SymbolFlags.Alias) { const declaration = getDeclarationOfAliasSymbol(symbol); return !!declaration && declaration.kind === SyntaxKind.NamespaceImport; } } } return false; } function checkReferenceExpression(expr: Expression, invalidReferenceMessage: DiagnosticMessage, invalidOptionalChainMessage: DiagnosticMessage): boolean { // References are combinations of identifiers, parentheses, and property accesses. const node = skipOuterExpressions(expr, OuterExpressionKinds.Assertions | OuterExpressionKinds.Parentheses); if (node.kind !== SyntaxKind.Identifier && !isAccessExpression(node)) { error(expr, invalidReferenceMessage); return false; } if (node.flags & NodeFlags.OptionalChain) { error(expr, invalidOptionalChainMessage); return false; } return true; } function checkDeleteExpression(node: DeleteExpression): Type { checkExpression(node.expression); const expr = skipParentheses(node.expression); if (!isAccessExpression(expr)) { error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_a_property_reference); return booleanType; } if (isPropertyAccessExpression(expr) && isPrivateIdentifier(expr.name)) { error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_private_identifier); } const links = getNodeLinks(expr); const symbol = getExportSymbolOfValueSymbolIfExported(links.resolvedSymbol); if (symbol) { if (isReadonlySymbol(symbol)) { error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_read_only_property); } checkDeleteExpressionMustBeOptional(expr, getTypeOfSymbol(symbol)); } return booleanType; } function checkDeleteExpressionMustBeOptional(expr: AccessExpression, type: Type) { const AnyOrUnknownOrNeverFlags = TypeFlags.AnyOrUnknown | TypeFlags.Never; if (strictNullChecks && !(type.flags & AnyOrUnknownOrNeverFlags) && !(getFalsyFlags(type) & TypeFlags.Undefined)) { error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_optional); } } function checkTypeOfExpression(node: TypeOfExpression): Type { checkExpression(node.expression); return typeofType; } function checkVoidExpression(node: VoidExpression): Type { checkExpression(node.expression); return undefinedWideningType; } function checkAwaitExpression(node: AwaitExpression): Type { // Grammar checking if (produceDiagnostics) { if (!(node.flags & NodeFlags.AwaitContext)) { if (isInTopLevelContext(node)) { const sourceFile = getSourceFileOfNode(node); if (!hasParseDiagnostics(sourceFile)) { let span: TextSpan | undefined; if (!isEffectiveExternalModule(sourceFile, compilerOptions)) { if (!span) span = getSpanOfTokenAtPosition(sourceFile, node.pos); const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.await_expressions_are_only_allowed_at_the_top_level_of_a_file_when_that_file_is_a_module_but_this_file_has_no_imports_or_exports_Consider_adding_an_empty_export_to_make_this_file_a_module); diagnostics.add(diagnostic); } if ((moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.System) || languageVersion < ScriptTarget.ES2017) { span = getSpanOfTokenAtPosition(sourceFile, node.pos); const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_esnext_or_system_and_the_target_option_is_set_to_es2017_or_higher); diagnostics.add(diagnostic); } } } else { // use of 'await' in non-async function const sourceFile = getSourceFileOfNode(node); if (!hasParseDiagnostics(sourceFile)) { const span = getSpanOfTokenAtPosition(sourceFile, node.pos); const diagnostic = createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.await_expressions_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules); const func = getContainingFunction(node); if (func && func.kind !== SyntaxKind.Constructor && (getFunctionFlags(func) & FunctionFlags.Async) === 0) { const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async); addRelatedInfo(diagnostic, relatedInfo); } diagnostics.add(diagnostic); } } } if (isInParameterInitializerBeforeContainingFunction(node)) { error(node, Diagnostics.await_expressions_cannot_be_used_in_a_parameter_initializer); } } const operandType = checkExpression(node.expression); const awaitedType = checkAwaitedType(operandType, node, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); if (awaitedType === operandType && awaitedType !== errorType && !(operandType.flags & TypeFlags.AnyOrUnknown)) { addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.await_has_no_effect_on_the_type_of_this_expression)); } return awaitedType; } function checkPrefixUnaryExpression(node: PrefixUnaryExpression): Type { const operandType = checkExpression(node.operand); if (operandType === silentNeverType) { return silentNeverType; } switch (node.operand.kind) { case SyntaxKind.NumericLiteral: switch (node.operator) { case SyntaxKind.MinusToken: return getFreshTypeOfLiteralType(getLiteralType(-(node.operand as NumericLiteral).text)); case SyntaxKind.PlusToken: return getFreshTypeOfLiteralType(getLiteralType(+(node.operand as NumericLiteral).text)); } break; case SyntaxKind.BigIntLiteral: if (node.operator === SyntaxKind.MinusToken) { return getFreshTypeOfLiteralType(getLiteralType({ negative: true, base10Value: parsePseudoBigInt((node.operand as BigIntLiteral).text) })); } } switch (node.operator) { case SyntaxKind.PlusToken: case SyntaxKind.MinusToken: case SyntaxKind.TildeToken: checkNonNullType(operandType, node.operand); if (maybeTypeOfKind(operandType, TypeFlags.ESSymbolLike)) { error(node.operand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(node.operator)); } if (node.operator === SyntaxKind.PlusToken) { if (maybeTypeOfKind(operandType, TypeFlags.BigIntLike)) { error(node.operand, Diagnostics.Operator_0_cannot_be_applied_to_type_1, tokenToString(node.operator), typeToString(getBaseTypeOfLiteralType(operandType))); } return numberType; } return getUnaryResultType(operandType); case SyntaxKind.ExclamationToken: checkTruthinessExpression(node.operand); const facts = getTypeFacts(operandType) & (TypeFacts.Truthy | TypeFacts.Falsy); return facts === TypeFacts.Truthy ? falseType : facts === TypeFacts.Falsy ? trueType : booleanType; case SyntaxKind.PlusPlusToken: case SyntaxKind.MinusMinusToken: const ok = checkArithmeticOperandType(node.operand, checkNonNullType(operandType, node.operand), Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type); if (ok) { // run check only if former checks succeeded to avoid reporting cascading errors checkReferenceExpression( node.operand, Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access); } return getUnaryResultType(operandType); } return errorType; } function checkPostfixUnaryExpression(node: PostfixUnaryExpression): Type { const operandType = checkExpression(node.operand); if (operandType === silentNeverType) { return silentNeverType; } const ok = checkArithmeticOperandType( node.operand, checkNonNullType(operandType, node.operand), Diagnostics.An_arithmetic_operand_must_be_of_type_any_number_bigint_or_an_enum_type); if (ok) { // run check only if former checks succeeded to avoid reporting cascading errors checkReferenceExpression( node.operand, Diagnostics.The_operand_of_an_increment_or_decrement_operator_must_be_a_variable_or_a_property_access, Diagnostics.The_operand_of_an_increment_or_decrement_operator_may_not_be_an_optional_property_access); } return getUnaryResultType(operandType); } function getUnaryResultType(operandType: Type): Type { if (maybeTypeOfKind(operandType, TypeFlags.BigIntLike)) { return isTypeAssignableToKind(operandType, TypeFlags.AnyOrUnknown) || maybeTypeOfKind(operandType, TypeFlags.NumberLike) ? numberOrBigIntType : bigintType; } // If it's not a bigint type, implicit coercion will result in a number return numberType; } // Return true if type might be of the given kind. A union or intersection type might be of a given // kind if at least one constituent type is of the given kind. function maybeTypeOfKind(type: Type, kind: TypeFlags): boolean { if (type.flags & kind) { return true; } if (type.flags & TypeFlags.UnionOrIntersection) { const types = (type).types; for (const t of types) { if (maybeTypeOfKind(t, kind)) { return true; } } } return false; } function isTypeAssignableToKind(source: Type, kind: TypeFlags, strict?: boolean): boolean { if (source.flags & kind) { return true; } if (strict && source.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Void | TypeFlags.Undefined | TypeFlags.Null)) { return false; } return !!(kind & TypeFlags.NumberLike) && isTypeAssignableTo(source, numberType) || !!(kind & TypeFlags.BigIntLike) && isTypeAssignableTo(source, bigintType) || !!(kind & TypeFlags.StringLike) && isTypeAssignableTo(source, stringType) || !!(kind & TypeFlags.BooleanLike) && isTypeAssignableTo(source, booleanType) || !!(kind & TypeFlags.Void) && isTypeAssignableTo(source, voidType) || !!(kind & TypeFlags.Never) && isTypeAssignableTo(source, neverType) || !!(kind & TypeFlags.Null) && isTypeAssignableTo(source, nullType) || !!(kind & TypeFlags.Undefined) && isTypeAssignableTo(source, undefinedType) || !!(kind & TypeFlags.ESSymbol) && isTypeAssignableTo(source, esSymbolType) || !!(kind & TypeFlags.NonPrimitive) && isTypeAssignableTo(source, nonPrimitiveType); } function allTypesAssignableToKind(source: Type, kind: TypeFlags, strict?: boolean): boolean { return source.flags & TypeFlags.Union ? every((source as UnionType).types, subType => allTypesAssignableToKind(subType, kind, strict)) : isTypeAssignableToKind(source, kind, strict); } function isConstEnumObjectType(type: Type): boolean { return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isConstEnumSymbol(type.symbol); } function isConstEnumSymbol(symbol: Symbol): boolean { return (symbol.flags & SymbolFlags.ConstEnum) !== 0; } function checkInstanceOfExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { if (leftType === silentNeverType || rightType === silentNeverType) { return silentNeverType; } // TypeScript 1.0 spec (April 2014): 4.15.4 // The instanceof operator requires the left operand to be of type Any, an object type, or a type parameter type, // and the right operand to be of type Any, a subtype of the 'Function' interface type, or have a call or construct signature. // The result is always of the Boolean primitive type. // NOTE: do not raise error if leftType is unknown as related error was already reported if (!isTypeAny(leftType) && allTypesAssignableToKind(leftType, TypeFlags.Primitive)) { error(left, Diagnostics.The_left_hand_side_of_an_instanceof_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); } // NOTE: do not raise error if right is unknown as related error was already reported if (!(isTypeAny(rightType) || typeHasCallOrConstructSignatures(rightType) || isTypeSubtypeOf(rightType, globalFunctionType))) { error(right, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_be_of_type_any_or_of_a_type_assignable_to_the_Function_interface_type); } return booleanType; } function checkInExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { if (leftType === silentNeverType || rightType === silentNeverType) { return silentNeverType; } leftType = checkNonNullType(leftType, left); rightType = checkNonNullType(rightType, right); // TypeScript 1.0 spec (April 2014): 4.15.5 // The in operator requires the left operand to be of type Any, the String primitive type, or the Number primitive type, // and the right operand to be of type Any, an object type, or a type parameter type. // The result is always of the Boolean primitive type. if (!(allTypesAssignableToKind(leftType, TypeFlags.StringLike | TypeFlags.NumberLike | TypeFlags.ESSymbolLike) || isTypeAssignableToKind(leftType, TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping | TypeFlags.TypeParameter))) { error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_type_any_string_number_or_symbol); } if (!allTypesAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) { error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); } return booleanType; } function checkObjectLiteralAssignment(node: ObjectLiteralExpression, sourceType: Type, rightIsThis?: boolean): Type { const properties = node.properties; if (strictNullChecks && properties.length === 0) { return checkNonNullType(sourceType, node); } for (let i = 0; i < properties.length; i++) { checkObjectLiteralDestructuringPropertyAssignment(node, sourceType, i, properties, rightIsThis); } return sourceType; } /** Note: If property cannot be a SpreadAssignment, then allProperties does not need to be provided */ function checkObjectLiteralDestructuringPropertyAssignment(node: ObjectLiteralExpression, objectLiteralType: Type, propertyIndex: number, allProperties?: NodeArray, rightIsThis = false) { const properties = node.properties; const property = properties[propertyIndex]; if (property.kind === SyntaxKind.PropertyAssignment || property.kind === SyntaxKind.ShorthandPropertyAssignment) { const name = property.name; const exprType = getLiteralTypeFromPropertyName(name); if (isTypeUsableAsPropertyName(exprType)) { const text = getPropertyNameFromType(exprType); const prop = getPropertyOfType(objectLiteralType, text); if (prop) { markPropertyAsReferenced(prop, property, rightIsThis); checkPropertyAccessibility(property, /*isSuper*/ false, objectLiteralType, prop); } } const elementType = getIndexedAccessType(objectLiteralType, exprType, /*noUncheckedIndexedAccessCandidate*/ undefined, name, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, AccessFlags.ExpressionPosition); const type = getFlowTypeOfDestructuring(property, elementType); return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type); } else if (property.kind === SyntaxKind.SpreadAssignment) { if (propertyIndex < properties.length - 1) { error(property, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); } else { if (languageVersion < ScriptTarget.ESNext) { checkExternalEmitHelpers(property, ExternalEmitHelpers.Rest); } const nonRestNames: PropertyName[] = []; if (allProperties) { for (const otherProperty of allProperties) { if (!isSpreadAssignment(otherProperty)) { nonRestNames.push(otherProperty.name); } } } const type = getRestType(objectLiteralType, nonRestNames, objectLiteralType.symbol); checkGrammarForDisallowedTrailingComma(allProperties, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); return checkDestructuringAssignment(property.expression, type); } } else { error(property, Diagnostics.Property_assignment_expected); } } function checkArrayLiteralAssignment(node: ArrayLiteralExpression, sourceType: Type, checkMode?: CheckMode): Type { const elements = node.elements; if (languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { checkExternalEmitHelpers(node, ExternalEmitHelpers.Read); } // 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 possiblyOutOfBoundsType = checkIteratedTypeOrElementType(IterationUse.Destructuring | IterationUse.PossiblyOutOfBounds, sourceType, undefinedType, node) || errorType; let inBoundsType: Type | undefined = compilerOptions.noUncheckedIndexedAccess ? undefined: possiblyOutOfBoundsType; for (let i = 0; i < elements.length; i++) { let type = possiblyOutOfBoundsType; if (node.elements[i].kind === SyntaxKind.SpreadElement) { type = inBoundsType = inBoundsType ?? (checkIteratedTypeOrElementType(IterationUse.Destructuring, sourceType, undefinedType, node) || errorType); } checkArrayLiteralDestructuringElementAssignment(node, sourceType, i, type, checkMode); } return sourceType; } function checkArrayLiteralDestructuringElementAssignment(node: ArrayLiteralExpression, sourceType: Type, elementIndex: number, elementType: Type, checkMode?: CheckMode) { const elements = node.elements; const element = elements[elementIndex]; if (element.kind !== SyntaxKind.OmittedExpression) { if (element.kind !== SyntaxKind.SpreadElement) { const indexType = getLiteralType(elementIndex); if (isArrayLikeType(sourceType)) { // We create a synthetic expression so that getIndexedAccessType doesn't get confused // when the element is a SyntaxKind.ElementAccessExpression. const accessFlags = AccessFlags.ExpressionPosition | (hasDefaultValue(element) ? AccessFlags.NoTupleBoundsCheck : 0); const elementType = getIndexedAccessTypeOrUndefined(sourceType, indexType, /*noUncheckedIndexedAccessCandidate*/ undefined, createSyntheticExpression(element, indexType), accessFlags) || errorType; const assignedType = hasDefaultValue(element) ? getTypeWithFacts(elementType, TypeFacts.NEUndefined) : elementType; const type = getFlowTypeOfDestructuring(element, assignedType); return checkDestructuringAssignment(element, type, checkMode); } return checkDestructuringAssignment(element, elementType, checkMode); } if (elementIndex < elements.length - 1) { error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); } else { const restExpression = (element).expression; if (restExpression.kind === SyntaxKind.BinaryExpression && (restExpression).operatorToken.kind === SyntaxKind.EqualsToken) { error((restExpression).operatorToken, Diagnostics.A_rest_element_cannot_have_an_initializer); } else { checkGrammarForDisallowedTrailingComma(node.elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); const type = everyType(sourceType, isTupleType) ? mapType(sourceType, t => sliceTupleType(t, elementIndex)) : createArrayType(elementType); return checkDestructuringAssignment(restExpression, type, checkMode); } } } return undefined; } function checkDestructuringAssignment(exprOrAssignment: Expression | ShorthandPropertyAssignment, sourceType: Type, checkMode?: CheckMode, rightIsThis?: boolean): Type { let target: Expression; if (exprOrAssignment.kind === SyntaxKind.ShorthandPropertyAssignment) { const prop = exprOrAssignment; if (prop.objectAssignmentInitializer) { // In strict null checking mode, if a default value of a non-undefined type is specified, remove // undefined from the final type. if (strictNullChecks && !(getFalsyFlags(checkExpression(prop.objectAssignmentInitializer)) & TypeFlags.Undefined)) { sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); } checkBinaryLikeExpression(prop.name, prop.equalsToken!, prop.objectAssignmentInitializer, checkMode); } target = (exprOrAssignment).name; } else { target = exprOrAssignment; } if (target.kind === SyntaxKind.BinaryExpression && (target).operatorToken.kind === SyntaxKind.EqualsToken) { checkBinaryExpression(target, checkMode); target = (target).left; } if (target.kind === SyntaxKind.ObjectLiteralExpression) { return checkObjectLiteralAssignment(target, sourceType, rightIsThis); } if (target.kind === SyntaxKind.ArrayLiteralExpression) { return checkArrayLiteralAssignment(target, sourceType, checkMode); } return checkReferenceAssignment(target, sourceType, checkMode); } function checkReferenceAssignment(target: Expression, sourceType: Type, checkMode?: CheckMode): Type { const targetType = checkExpression(target, checkMode); const error = target.parent.kind === SyntaxKind.SpreadAssignment ? Diagnostics.The_target_of_an_object_rest_assignment_must_be_a_variable_or_a_property_access : Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access; const optionalError = target.parent.kind === SyntaxKind.SpreadAssignment ? Diagnostics.The_target_of_an_object_rest_assignment_may_not_be_an_optional_property_access : Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access; if (checkReferenceExpression(target, error, optionalError)) { checkTypeAssignableToAndOptionallyElaborate(sourceType, targetType, target, target); } if (isPrivateIdentifierPropertyAccessExpression(target)) { checkExternalEmitHelpers(target.parent, ExternalEmitHelpers.ClassPrivateFieldSet); } return sourceType; } /** * This is a *shallow* check: An expression is side-effect-free if the * evaluation of the expression *itself* cannot produce side effects. * For example, x++ / 3 is side-effect free because the / operator * does not have side effects. * The intent is to "smell test" an expression for correctness in positions where * its value is discarded (e.g. the left side of the comma operator). */ function isSideEffectFree(node: Node): boolean { node = skipParentheses(node); switch (node.kind) { case SyntaxKind.Identifier: case SyntaxKind.StringLiteral: case SyntaxKind.RegularExpressionLiteral: case SyntaxKind.TaggedTemplateExpression: case SyntaxKind.TemplateExpression: case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.NumericLiteral: case SyntaxKind.BigIntLiteral: case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: case SyntaxKind.NullKeyword: case SyntaxKind.UndefinedKeyword: case SyntaxKind.FunctionExpression: case SyntaxKind.ClassExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.ArrayLiteralExpression: case SyntaxKind.ObjectLiteralExpression: case SyntaxKind.TypeOfExpression: case SyntaxKind.NonNullExpression: case SyntaxKind.JsxSelfClosingElement: case SyntaxKind.JsxElement: return true; case SyntaxKind.ConditionalExpression: return isSideEffectFree((node as ConditionalExpression).whenTrue) && isSideEffectFree((node as ConditionalExpression).whenFalse); case SyntaxKind.BinaryExpression: if (isAssignmentOperator((node as BinaryExpression).operatorToken.kind)) { return false; } return isSideEffectFree((node as BinaryExpression).left) && isSideEffectFree((node as BinaryExpression).right); case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.PostfixUnaryExpression: // Unary operators ~, !, +, and - have no side effects. // The rest do. switch ((node as PrefixUnaryExpression).operator) { case SyntaxKind.ExclamationToken: case SyntaxKind.PlusToken: case SyntaxKind.MinusToken: case SyntaxKind.TildeToken: return true; } return false; // Some forms listed here for clarity case SyntaxKind.VoidExpression: // Explicit opt-out case SyntaxKind.TypeAssertionExpression: // Not SEF, but can produce useful type warnings case SyntaxKind.AsExpression: // Not SEF, but can produce useful type warnings default: return false; } } function isTypeEqualityComparableTo(source: Type, target: Type) { return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target); } const enum CheckBinaryExpressionState { MaybeCheckLeft, CheckRight, FinishCheck } function checkBinaryExpression(node: BinaryExpression, checkMode?: CheckMode) { const workStacks: { expr: BinaryExpression[], state: CheckBinaryExpressionState[], leftType: (Type | undefined)[] } = { expr: [node], state: [CheckBinaryExpressionState.MaybeCheckLeft], leftType: [undefined] }; let stackIndex = 0; let lastResult: Type | undefined; while (stackIndex >= 0) { node = workStacks.expr[stackIndex]; switch (workStacks.state[stackIndex]) { case CheckBinaryExpressionState.MaybeCheckLeft: { if (isInJSFile(node) && getAssignedExpandoInitializer(node)) { finishInvocation(checkExpression(node.right, checkMode)); break; } checkGrammarNullishCoalesceWithLogicalExpression(node); const operator = node.operatorToken.kind; if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) { finishInvocation(checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword)); break; } advanceState(CheckBinaryExpressionState.CheckRight); maybeCheckExpression(node.left); break; } case CheckBinaryExpressionState.CheckRight: { const leftType = lastResult!; workStacks.leftType[stackIndex] = leftType; const operator = node.operatorToken.kind; if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { checkTruthinessOfType(leftType, node.left); } advanceState(CheckBinaryExpressionState.FinishCheck); maybeCheckExpression(node.right); break; } case CheckBinaryExpressionState.FinishCheck: { const leftType = workStacks.leftType[stackIndex]!; const rightType = lastResult!; finishInvocation(checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node)); break; } default: return Debug.fail(`Invalid state ${workStacks.state[stackIndex]} for checkBinaryExpression`); } } return lastResult!; function finishInvocation(result: Type) { lastResult = result; stackIndex--; } /** * Note that `advanceState` sets the _current_ head state, and that `maybeCheckExpression` potentially pushes on a new * head state; so `advanceState` must be called before any `maybeCheckExpression` during a state's execution. */ function advanceState(nextState: CheckBinaryExpressionState) { workStacks.state[stackIndex] = nextState; } function maybeCheckExpression(node: Expression) { if (isBinaryExpression(node)) { stackIndex++; workStacks.expr[stackIndex] = node; workStacks.state[stackIndex] = CheckBinaryExpressionState.MaybeCheckLeft; workStacks.leftType[stackIndex] = undefined; } else { lastResult = checkExpression(node, checkMode); } } } function checkGrammarNullishCoalesceWithLogicalExpression(node: BinaryExpression) { const { left, operatorToken, right } = node; if (operatorToken.kind === SyntaxKind.QuestionQuestionToken) { if (isBinaryExpression(left) && (left.operatorToken.kind === SyntaxKind.BarBarToken || left.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { grammarErrorOnNode(left, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(left.operatorToken.kind), tokenToString(operatorToken.kind)); } if (isBinaryExpression(right) && (right.operatorToken.kind === SyntaxKind.BarBarToken || right.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken)) { grammarErrorOnNode(right, Diagnostics._0_and_1_operations_cannot_be_mixed_without_parentheses, tokenToString(right.operatorToken.kind), tokenToString(operatorToken.kind)); } } } // Note that this and `checkBinaryExpression` above should behave mostly the same, except this elides some // expression-wide checks and does not use a work stack to fold nested binary expressions into the same callstack frame function checkBinaryLikeExpression(left: Expression, operatorToken: Node, right: Expression, checkMode?: CheckMode, errorNode?: Node): Type { const operator = operatorToken.kind; if (operator === SyntaxKind.EqualsToken && (left.kind === SyntaxKind.ObjectLiteralExpression || left.kind === SyntaxKind.ArrayLiteralExpression)) { return checkDestructuringAssignment(left, checkExpression(right, checkMode), checkMode, right.kind === SyntaxKind.ThisKeyword); } let leftType: Type; if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { leftType = checkTruthinessExpression(left, checkMode); } else { leftType = checkExpression(left, checkMode); } const rightType = checkExpression(right, checkMode); return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, errorNode); } function checkBinaryLikeExpressionWorker( left: Expression, operatorToken: Node, right: Expression, leftType: Type, rightType: Type, errorNode?: Node ): Type { const operator = operatorToken.kind; switch (operator) { case SyntaxKind.AsteriskToken: case SyntaxKind.AsteriskAsteriskToken: case SyntaxKind.AsteriskEqualsToken: case SyntaxKind.AsteriskAsteriskEqualsToken: case SyntaxKind.SlashToken: case SyntaxKind.SlashEqualsToken: case SyntaxKind.PercentToken: case SyntaxKind.PercentEqualsToken: case SyntaxKind.MinusToken: case SyntaxKind.MinusEqualsToken: case SyntaxKind.LessThanLessThanToken: case SyntaxKind.LessThanLessThanEqualsToken: case SyntaxKind.GreaterThanGreaterThanToken: case SyntaxKind.GreaterThanGreaterThanEqualsToken: case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: case SyntaxKind.BarToken: case SyntaxKind.BarEqualsToken: case SyntaxKind.CaretToken: case SyntaxKind.CaretEqualsToken: case SyntaxKind.AmpersandToken: case SyntaxKind.AmpersandEqualsToken: if (leftType === silentNeverType || rightType === silentNeverType) { return silentNeverType; } leftType = checkNonNullType(leftType, left); rightType = checkNonNullType(rightType, right); let suggestedOperator: SyntaxKind | undefined; // if a user tries to apply a bitwise operator to 2 boolean operands // try and return them a helpful suggestion if ((leftType.flags & TypeFlags.BooleanLike) && (rightType.flags & TypeFlags.BooleanLike) && (suggestedOperator = getSuggestedBooleanOperator(operatorToken.kind)) !== undefined) { error(errorNode || operatorToken, Diagnostics.The_0_operator_is_not_allowed_for_boolean_types_Consider_using_1_instead, tokenToString(operatorToken.kind), tokenToString(suggestedOperator)); return numberType; } else { // otherwise just check each operand separately and report errors as normal const leftOk = checkArithmeticOperandType(left, leftType, Diagnostics.The_left_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, /*isAwaitValid*/ true); const rightOk = checkArithmeticOperandType(right, rightType, Diagnostics.The_right_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_bigint_or_an_enum_type, /*isAwaitValid*/ true); let resultType: Type; // If both are any or unknown, allow operation; assume it will resolve to number if ((isTypeAssignableToKind(leftType, TypeFlags.AnyOrUnknown) && isTypeAssignableToKind(rightType, TypeFlags.AnyOrUnknown)) || // Or, if neither could be bigint, implicit coercion results in a number result !(maybeTypeOfKind(leftType, TypeFlags.BigIntLike) || maybeTypeOfKind(rightType, TypeFlags.BigIntLike)) ) { resultType = numberType; } // At least one is assignable to bigint, so check that both are else if (bothAreBigIntLike(leftType, rightType)) { switch (operator) { case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: reportOperatorError(); break; case SyntaxKind.AsteriskAsteriskToken: case SyntaxKind.AsteriskAsteriskEqualsToken: if (languageVersion < ScriptTarget.ES2016) { error(errorNode, Diagnostics.Exponentiation_cannot_be_performed_on_bigint_values_unless_the_target_option_is_set_to_es2016_or_later); } } resultType = bigintType; } // Exactly one of leftType/rightType is assignable to bigint else { reportOperatorError(bothAreBigIntLike); resultType = errorType; } if (leftOk && rightOk) { checkAssignmentOperator(resultType); } return resultType; } case SyntaxKind.PlusToken: case SyntaxKind.PlusEqualsToken: if (leftType === silentNeverType || rightType === silentNeverType) { return silentNeverType; } if (!isTypeAssignableToKind(leftType, TypeFlags.StringLike) && !isTypeAssignableToKind(rightType, TypeFlags.StringLike)) { leftType = checkNonNullType(leftType, left); rightType = checkNonNullType(rightType, right); } let resultType: Type | undefined; if (isTypeAssignableToKind(leftType, TypeFlags.NumberLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.NumberLike, /*strict*/ true)) { // Operands of an enum type are treated as having the primitive type Number. // If both operands are of the Number primitive type, the result is of the Number primitive type. resultType = numberType; } else if (isTypeAssignableToKind(leftType, TypeFlags.BigIntLike, /*strict*/ true) && isTypeAssignableToKind(rightType, TypeFlags.BigIntLike, /*strict*/ true)) { // If both operands are of the BigInt primitive type, the result is of the BigInt primitive type. resultType = bigintType; } else if (isTypeAssignableToKind(leftType, TypeFlags.StringLike, /*strict*/ true) || isTypeAssignableToKind(rightType, TypeFlags.StringLike, /*strict*/ true)) { // If one or both operands are of the String primitive type, the result is of the String primitive type. resultType = stringType; } else if (isTypeAny(leftType) || isTypeAny(rightType)) { // Otherwise, the result is of type Any. // NOTE: unknown type here denotes error type. Old compiler treated this case as any type so do we. resultType = leftType === errorType || rightType === errorType ? errorType : anyType; } // Symbols are not allowed at all in arithmetic expressions if (resultType && !checkForDisallowedESSymbolOperand(operator)) { return resultType; } if (!resultType) { // Types that have a reasonably good chance of being a valid operand type. // If both types have an awaited type of one of these, we'll assume the user // might be missing an await without doing an exhaustive check that inserting // await(s) will actually be a completely valid binary expression. const closeEnoughKind = TypeFlags.NumberLike | TypeFlags.BigIntLike | TypeFlags.StringLike | TypeFlags.AnyOrUnknown; reportOperatorError((left, right) => isTypeAssignableToKind(left, closeEnoughKind) && isTypeAssignableToKind(right, closeEnoughKind)); return anyType; } if (operator === SyntaxKind.PlusEqualsToken) { checkAssignmentOperator(resultType); } return resultType; case SyntaxKind.LessThanToken: case SyntaxKind.GreaterThanToken: case SyntaxKind.LessThanEqualsToken: case SyntaxKind.GreaterThanEqualsToken: if (checkForDisallowedESSymbolOperand(operator)) { leftType = getBaseTypeOfLiteralType(checkNonNullType(leftType, left)); rightType = getBaseTypeOfLiteralType(checkNonNullType(rightType, right)); reportOperatorErrorUnless((left, right) => isTypeComparableTo(left, right) || isTypeComparableTo(right, left) || ( isTypeAssignableTo(left, numberOrBigIntType) && isTypeAssignableTo(right, numberOrBigIntType))); } return booleanType; case SyntaxKind.EqualsEqualsToken: case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.ExclamationEqualsEqualsToken: reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left)); return booleanType; case SyntaxKind.InstanceOfKeyword: return checkInstanceOfExpression(left, right, leftType, rightType); case SyntaxKind.InKeyword: return checkInExpression(left, right, leftType, rightType); case SyntaxKind.AmpersandAmpersandToken: case SyntaxKind.AmpersandAmpersandEqualsToken: { const resultType = getTypeFacts(leftType) & TypeFacts.Truthy ? getUnionType([extractDefinitelyFalsyTypes(strictNullChecks ? leftType : getBaseTypeOfLiteralType(rightType)), rightType]) : leftType; if (operator === SyntaxKind.AmpersandAmpersandEqualsToken) { checkAssignmentOperator(rightType); } return resultType; } case SyntaxKind.BarBarToken: case SyntaxKind.BarBarEqualsToken: { const resultType = getTypeFacts(leftType) & TypeFacts.Falsy ? getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], UnionReduction.Subtype) : leftType; if (operator === SyntaxKind.BarBarEqualsToken) { checkAssignmentOperator(rightType); } return resultType; } case SyntaxKind.QuestionQuestionToken: case SyntaxKind.QuestionQuestionEqualsToken: { const resultType = getTypeFacts(leftType) & TypeFacts.EQUndefinedOrNull ? getUnionType([getNonNullableType(leftType), rightType], UnionReduction.Subtype) : leftType; if (operator === SyntaxKind.QuestionQuestionEqualsToken) { checkAssignmentOperator(rightType); } return resultType; } case SyntaxKind.EqualsToken: const declKind = isBinaryExpression(left.parent) ? getAssignmentDeclarationKind(left.parent) : AssignmentDeclarationKind.None; checkAssignmentDeclaration(declKind, rightType); if (isAssignmentDeclaration(declKind)) { if (!(rightType.flags & TypeFlags.Object) || declKind !== AssignmentDeclarationKind.ModuleExports && declKind !== AssignmentDeclarationKind.Prototype && !isEmptyObjectType(rightType) && !isFunctionObjectType(rightType as ObjectType) && !(getObjectFlags(rightType) & ObjectFlags.Class)) { // don't check assignability of module.exports=, C.prototype=, or expando types because they will necessarily be incomplete checkAssignmentOperator(rightType); } return leftType; } else { checkAssignmentOperator(rightType); return getRegularTypeOfObjectLiteral(rightType); } case SyntaxKind.CommaToken: if (!compilerOptions.allowUnreachableCode && isSideEffectFree(left) && !isEvalNode(right)) { const sf = getSourceFileOfNode(left); const sourceText = sf.text; const start = skipTrivia(sourceText, left.pos); const isInDiag2657 = sf.parseDiagnostics.some(diag => { if (diag.code !== Diagnostics.JSX_expressions_must_have_one_parent_element.code) return false; return textSpanContainsPosition(diag, start); }); if (!isInDiag2657) error(left, Diagnostics.Left_side_of_comma_operator_is_unused_and_has_no_side_effects); } return rightType; default: return Debug.fail(); } function bothAreBigIntLike(left: Type, right: Type): boolean { return isTypeAssignableToKind(left, TypeFlags.BigIntLike) && isTypeAssignableToKind(right, TypeFlags.BigIntLike); } function checkAssignmentDeclaration(kind: AssignmentDeclarationKind, rightType: Type) { if (kind === AssignmentDeclarationKind.ModuleExports) { for (const prop of getPropertiesOfObjectType(rightType)) { const propType = getTypeOfSymbol(prop); if (propType.symbol && propType.symbol.flags & SymbolFlags.Class) { const name = prop.escapedName; const symbol = resolveName(prop.valueDeclaration, name, SymbolFlags.Type, undefined, name, /*isUse*/ false); if (symbol && symbol.declarations.some(isJSDocTypedefTag)) { addDuplicateDeclarationErrorsForSymbols(symbol, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name), prop); addDuplicateDeclarationErrorsForSymbols(prop, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name), symbol); } } } } } function isEvalNode(node: Expression) { return node.kind === SyntaxKind.Identifier && (node as Identifier).escapedText === "eval"; } // Return true if there was no error, false if there was an error. function checkForDisallowedESSymbolOperand(operator: SyntaxKind): boolean { const offendingSymbolOperand = maybeTypeOfKind(leftType, TypeFlags.ESSymbolLike) ? left : maybeTypeOfKind(rightType, TypeFlags.ESSymbolLike) ? right : undefined; if (offendingSymbolOperand) { error(offendingSymbolOperand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(operator)); return false; } return true; } function getSuggestedBooleanOperator(operator: SyntaxKind): SyntaxKind | undefined { switch (operator) { case SyntaxKind.BarToken: case SyntaxKind.BarEqualsToken: return SyntaxKind.BarBarToken; case SyntaxKind.CaretToken: case SyntaxKind.CaretEqualsToken: return SyntaxKind.ExclamationEqualsEqualsToken; case SyntaxKind.AmpersandToken: case SyntaxKind.AmpersandEqualsToken: return SyntaxKind.AmpersandAmpersandToken; default: return undefined; } } function checkAssignmentOperator(valueType: Type): void { if (produceDiagnostics && isAssignmentOperator(operator)) { // TypeScript 1.0 spec (April 2014): 4.17 // An assignment of the form // VarExpr = ValueExpr // requires VarExpr to be classified as a reference // A compound assignment furthermore requires VarExpr to be classified as a reference (section 4.1) // and the type of the non-compound operation to be assignable to the type of VarExpr. if (checkReferenceExpression(left, Diagnostics.The_left_hand_side_of_an_assignment_expression_must_be_a_variable_or_a_property_access, Diagnostics.The_left_hand_side_of_an_assignment_expression_may_not_be_an_optional_property_access) && (!isIdentifier(left) || unescapeLeadingUnderscores(left.escapedText) !== "exports")) { // to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported checkTypeAssignableToAndOptionallyElaborate(valueType, leftType, left, right); } } } function isAssignmentDeclaration(kind: AssignmentDeclarationKind) { switch (kind) { case AssignmentDeclarationKind.ModuleExports: return true; case AssignmentDeclarationKind.ExportsProperty: case AssignmentDeclarationKind.Property: case AssignmentDeclarationKind.Prototype: case AssignmentDeclarationKind.PrototypeProperty: case AssignmentDeclarationKind.ThisProperty: const symbol = getSymbolOfNode(left); const init = getAssignedExpandoInitializer(right); return !!init && isObjectLiteralExpression(init) && !!symbol?.exports?.size; default: return false; } } /** * Returns true if an error is reported */ function reportOperatorErrorUnless(typesAreCompatible: (left: Type, right: Type) => boolean): boolean { if (!typesAreCompatible(leftType, rightType)) { reportOperatorError(typesAreCompatible); return true; } return false; } function reportOperatorError(isRelated?: (left: Type, right: Type) => boolean) { let wouldWorkWithAwait = false; const errNode = errorNode || operatorToken; if (isRelated) { const awaitedLeftType = getAwaitedType(leftType); const awaitedRightType = getAwaitedType(rightType); wouldWorkWithAwait = !(awaitedLeftType === leftType && awaitedRightType === rightType) && !!(awaitedLeftType && awaitedRightType) && isRelated(awaitedLeftType, awaitedRightType); } let effectiveLeft = leftType; let effectiveRight = rightType; if (!wouldWorkWithAwait && isRelated) { [effectiveLeft, effectiveRight] = getBaseTypesIfUnrelated(leftType, rightType, isRelated); } const [leftStr, rightStr] = getTypeNamesForErrorDisplay(effectiveLeft, effectiveRight); if (!tryGiveBetterPrimaryError(errNode, wouldWorkWithAwait, leftStr, rightStr)) { errorAndMaybeSuggestAwait( errNode, wouldWorkWithAwait, Diagnostics.Operator_0_cannot_be_applied_to_types_1_and_2, tokenToString(operatorToken.kind), leftStr, rightStr, ); } } function tryGiveBetterPrimaryError(errNode: Node, maybeMissingAwait: boolean, leftStr: string, rightStr: string) { let typeName: string | undefined; switch (operatorToken.kind) { case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.EqualsEqualsToken: typeName = "false"; break; case SyntaxKind.ExclamationEqualsEqualsToken: case SyntaxKind.ExclamationEqualsToken: typeName = "true"; } if (typeName) { return errorAndMaybeSuggestAwait( errNode, maybeMissingAwait, Diagnostics.This_condition_will_always_return_0_since_the_types_1_and_2_have_no_overlap, typeName, leftStr, rightStr); } return undefined; } } function getBaseTypesIfUnrelated(leftType: Type, rightType: Type, isRelated: (left: Type, right: Type) => boolean): [Type, Type] { let effectiveLeft = leftType; let effectiveRight = rightType; const leftBase = getBaseTypeOfLiteralType(leftType); const rightBase = getBaseTypeOfLiteralType(rightType); if (!isRelated(leftBase, rightBase)) { effectiveLeft = leftBase; effectiveRight = rightBase; } return [ effectiveLeft, effectiveRight ]; } function checkYieldExpression(node: YieldExpression): Type { // Grammar checking if (produceDiagnostics) { if (!(node.flags & NodeFlags.YieldContext)) { grammarErrorOnFirstToken(node, Diagnostics.A_yield_expression_is_only_allowed_in_a_generator_body); } if (isInParameterInitializerBeforeContainingFunction(node)) { error(node, Diagnostics.yield_expressions_cannot_be_used_in_a_parameter_initializer); } } const func = getContainingFunction(node); if (!func) return anyType; const functionFlags = getFunctionFlags(func); if (!(functionFlags & FunctionFlags.Generator)) { // If the user's code is syntactically correct, the func should always have a star. After all, we are in a yield context. return anyType; } const isAsync = (functionFlags & FunctionFlags.Async) !== 0; if (node.asteriskToken) { // Async generator functions prior to ESNext require the __await, __asyncDelegator, // and __asyncValues helpers if (isAsync && languageVersion < ScriptTarget.ESNext) { checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncDelegatorIncludes); } // Generator functions prior to ES2015 require the __values helper if (!isAsync && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { checkExternalEmitHelpers(node, ExternalEmitHelpers.Values); } } // There is no point in doing an assignability check if the function // has no explicit return type because the return type is directly computed // from the yield expressions. const returnType = getReturnTypeFromAnnotation(func); const iterationTypes = returnType && getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsync); const signatureYieldType = iterationTypes && iterationTypes.yieldType || anyType; const signatureNextType = iterationTypes && iterationTypes.nextType || anyType; const resolvedSignatureNextType = isAsync ? getAwaitedType(signatureNextType) || anyType : signatureNextType; const yieldExpressionType = node.expression ? checkExpression(node.expression) : undefinedWideningType; const yieldedType = getYieldedTypeOfYieldExpression(node, yieldExpressionType, resolvedSignatureNextType, isAsync); if (returnType && yieldedType) { checkTypeAssignableToAndOptionallyElaborate(yieldedType, signatureYieldType, node.expression || node, node.expression); } if (node.asteriskToken) { const use = isAsync ? IterationUse.AsyncYieldStar : IterationUse.YieldStar; return getIterationTypeOfIterable(use, IterationTypeKind.Return, yieldExpressionType, node.expression) || anyType; } else if (returnType) { return getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, isAsync) || anyType; } return getContextualIterationType(IterationTypeKind.Next, func) || anyType; } function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type { const type = checkTruthinessExpression(node.condition); checkTestingKnownTruthyCallableType(node.condition, node.whenTrue, type); const type1 = checkExpression(node.whenTrue, checkMode); const type2 = checkExpression(node.whenFalse, checkMode); return getUnionType([type1, type2], UnionReduction.Subtype); } function checkTemplateExpression(node: TemplateExpression): Type { const texts = [node.head.text]; const types = []; for (const span of node.templateSpans) { const type = checkExpression(span.expression); if (maybeTypeOfKind(type, TypeFlags.ESSymbolLike)) { error(span.expression, Diagnostics.Implicit_conversion_of_a_symbol_to_a_string_will_fail_at_runtime_Consider_wrapping_this_expression_in_String); } texts.push(span.literal.text); types.push(isTypeAssignableTo(type, templateConstraintType) ? type : stringType); } return isConstContext(node) ? getTemplateLiteralType(texts, types) : stringType; } function getContextNode(node: Expression): Node { if (node.kind === SyntaxKind.JsxAttributes && !isJsxSelfClosingElement(node.parent)) { return node.parent.parent; // Needs to be the root JsxElement, so it encompasses the attributes _and_ the children (which are essentially part of the attributes) } return node; } function checkExpressionWithContextualType(node: Expression, contextualType: Type, inferenceContext: InferenceContext | undefined, checkMode: CheckMode): Type { const context = getContextNode(node); const saveContextualType = context.contextualType; const saveInferenceContext = context.inferenceContext; try { context.contextualType = contextualType; context.inferenceContext = inferenceContext; const type = checkExpression(node, checkMode | CheckMode.Contextual | (inferenceContext ? CheckMode.Inferential : 0)); // We strip literal freshness when an appropriate contextual type is present such that contextually typed // literals always preserve their literal types (otherwise they might widen during type inference). An alternative // here would be to not mark contextually typed literals as fresh in the first place. const result = maybeTypeOfKind(type, TypeFlags.Literal) && isLiteralOfContextualType(type, instantiateContextualType(contextualType, node)) ? getRegularTypeOfLiteralType(type) : type; return result; } finally { // In the event our operation is canceled or some other exception occurs, reset the contextual type // so that we do not accidentally hold onto an instance of the checker, as a Type created in the services layer // may hold onto the checker that created it. context.contextualType = saveContextualType; context.inferenceContext = saveInferenceContext; } } function checkExpressionCached(node: Expression | QualifiedName, checkMode?: CheckMode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { if (checkMode && checkMode !== CheckMode.Normal) { return checkExpression(node, checkMode); } // When computing a type that we're going to cache, we need to ignore any ongoing control flow // analysis because variables may have transient types in indeterminable states. Moving flowLoopStart // to the top of the stack ensures all transient types are computed from a known point. const saveFlowLoopStart = flowLoopStart; const saveFlowTypeCache = flowTypeCache; flowLoopStart = flowLoopCount; flowTypeCache = undefined; links.resolvedType = checkExpression(node, checkMode); flowTypeCache = saveFlowTypeCache; flowLoopStart = saveFlowLoopStart; } return links.resolvedType; } function isTypeAssertion(node: Expression) { node = skipParentheses(node); return node.kind === SyntaxKind.TypeAssertionExpression || node.kind === SyntaxKind.AsExpression; } function checkDeclarationInitializer(declaration: HasExpressionInitializer, contextualType?: Type | undefined) { const initializer = getEffectiveInitializer(declaration)!; const type = getQuickTypeOfExpression(initializer) || (contextualType ? checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, CheckMode.Normal) : checkExpressionCached(initializer)); return isParameter(declaration) && declaration.name.kind === SyntaxKind.ArrayBindingPattern && isTupleType(type) && !type.target.hasRestElement && getTypeReferenceArity(type) < declaration.name.elements.length ? padTupleType(type, declaration.name) : type; } function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) { const patternElements = pattern.elements; const elementTypes = getTypeArguments(type).slice(); const elementFlags = type.target.elementFlags.slice(); for (let i = getTypeReferenceArity(type); i < patternElements.length; i++) { const e = patternElements[i]; if (i < patternElements.length - 1 || !(e.kind === SyntaxKind.BindingElement && e.dotDotDotToken)) { elementTypes.push(!isOmittedExpression(e) && hasDefaultValue(e) ? getTypeFromBindingElement(e, /*includePatternInType*/ false, /*reportErrors*/ false) : anyType); elementFlags.push(ElementFlags.Optional); if (!isOmittedExpression(e) && !hasDefaultValue(e)) { reportImplicitAny(e, anyType); } } } return createTupleType(elementTypes, elementFlags, type.target.readonly); } function widenTypeInferredFromInitializer(declaration: HasExpressionInitializer, type: Type) { const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const || isDeclarationReadonly(declaration) ? type : getWidenedLiteralType(type); if (isInJSFile(declaration)) { if (widened.flags & TypeFlags.Nullable) { reportImplicitAny(declaration, anyType); return anyType; } else if (isEmptyArrayLiteralType(widened)) { reportImplicitAny(declaration, anyArrayType); return anyArrayType; } } return widened; } function isLiteralOfContextualType(candidateType: Type, contextualType: Type | undefined): boolean { if (contextualType) { if (contextualType.flags & TypeFlags.UnionOrIntersection) { const types = (contextualType).types; return some(types, t => isLiteralOfContextualType(candidateType, t)); } if (contextualType.flags & TypeFlags.InstantiableNonPrimitive) { // If the contextual type is a type variable constrained to a primitive type, consider // this a literal context for literals of that primitive type. For example, given a // type parameter 'T extends string', infer string literal types for T. const constraint = getBaseConstraintOfType(contextualType) || unknownType; return maybeTypeOfKind(constraint, TypeFlags.String) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) || maybeTypeOfKind(constraint, TypeFlags.Number) && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) || maybeTypeOfKind(constraint, TypeFlags.BigInt) && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) || maybeTypeOfKind(constraint, TypeFlags.ESSymbol) && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol) || isLiteralOfContextualType(candidateType, constraint); } // If the contextual type is a literal of a particular primitive type, we consider this a // literal context for all literals of that primitive type. return !!(contextualType.flags & (TypeFlags.StringLiteral | TypeFlags.Index | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) && maybeTypeOfKind(candidateType, TypeFlags.StringLiteral) || contextualType.flags & TypeFlags.NumberLiteral && maybeTypeOfKind(candidateType, TypeFlags.NumberLiteral) || contextualType.flags & TypeFlags.BigIntLiteral && maybeTypeOfKind(candidateType, TypeFlags.BigIntLiteral) || contextualType.flags & TypeFlags.BooleanLiteral && maybeTypeOfKind(candidateType, TypeFlags.BooleanLiteral) || contextualType.flags & TypeFlags.UniqueESSymbol && maybeTypeOfKind(candidateType, TypeFlags.UniqueESSymbol)); } return false; } function isConstContext(node: Expression): boolean { const parent = node.parent; return isAssertionExpression(parent) && isConstTypeReference(parent.type) || (isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) || (isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent) || isTemplateSpan(parent)) && isConstContext(parent.parent); } function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, contextualType?: Type, forceTuple?: boolean): Type { const type = checkExpression(node, checkMode, forceTuple); return isConstContext(node) ? getRegularTypeOfLiteralType(type) : isTypeAssertion(node) ? type : getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(arguments.length === 2 ? getContextualType(node) : contextualType, node)); } function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode): Type { // Do not use hasDynamicName here, because that returns false for well known symbols. // We want to perform checkComputedPropertyName for all computed properties, including // well known symbols. if (node.name.kind === SyntaxKind.ComputedPropertyName) { checkComputedPropertyName(node.name); } return checkExpressionForMutableLocation(node.initializer, checkMode); } function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode): Type { // Grammar checking checkGrammarMethod(node); // Do not use hasDynamicName here, because that returns false for well known symbols. // We want to perform checkComputedPropertyName for all computed properties, including // well known symbols. if (node.name.kind === SyntaxKind.ComputedPropertyName) { checkComputedPropertyName(node.name); } const uninstantiatedType = checkFunctionExpressionOrObjectLiteralMethod(node, checkMode); return instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); } function instantiateTypeWithSingleGenericCallSignature(node: Expression | MethodDeclaration | QualifiedName, type: Type, checkMode?: CheckMode) { if (checkMode && checkMode & (CheckMode.Inferential | CheckMode.SkipGenericFunctions)) { const callSignature = getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ true); const constructSignature = getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ true); const signature = callSignature || constructSignature; if (signature && signature.typeParameters) { const contextualType = getApparentTypeOfContextualType(node, ContextFlags.NoConstraints); if (contextualType) { const contextualSignature = getSingleSignature(getNonNullableType(contextualType), callSignature ? SignatureKind.Call : SignatureKind.Construct, /*allowMembers*/ false); if (contextualSignature && !contextualSignature.typeParameters) { if (checkMode & CheckMode.SkipGenericFunctions) { skippedGenericFunction(node, checkMode); return anyFunctionType; } const context = getInferenceContext(node)!; // We have an expression that is an argument of a generic function for which we are performing // type argument inference. The expression is of a function type with a single generic call // signature and a contextual function type with a single non-generic call signature. Now check // if the outer function returns a function type with a single non-generic call signature and // if some of the outer function type parameters have no inferences so far. If so, we can // potentially add inferred type parameters to the outer function return type. const returnType = context.signature && getReturnTypeOfSignature(context.signature); const returnSignature = returnType && getSingleCallOrConstructSignature(returnType); if (returnSignature && !returnSignature.typeParameters && !every(context.inferences, hasInferenceCandidates)) { // Instantiate the signature with its own type parameters as type arguments, possibly // renaming the type parameters to ensure they have unique names. const uniqueTypeParameters = getUniqueTypeParameters(context, signature.typeParameters); const instantiatedSignature = getSignatureInstantiationWithoutFillingInTypeArguments(signature, uniqueTypeParameters); // Infer from the parameters of the instantiated signature to the parameters of the // contextual signature starting with an empty set of inference candidates. const inferences = map(context.inferences, info => createInferenceInfo(info.typeParameter)); applyToParameterTypes(instantiatedSignature, contextualSignature, (source, target) => { inferTypes(inferences, source, target, /*priority*/ 0, /*contravariant*/ true); }); if (some(inferences, hasInferenceCandidates)) { // We have inference candidates, indicating that one or more type parameters are referenced // in the parameter types of the contextual signature. Now also infer from the return type. applyToReturnTypes(instantiatedSignature, contextualSignature, (source, target) => { inferTypes(inferences, source, target); }); // If the type parameters for which we produced candidates do not have any inferences yet, // we adopt the new inference candidates and add the type parameters of the expression type // to the set of inferred type parameters for the outer function return type. if (!hasOverlappingInferences(context.inferences, inferences)) { mergeInferences(context.inferences, inferences); context.inferredTypeParameters = concatenate(context.inferredTypeParameters, uniqueTypeParameters); return getOrCreateTypeFromSignature(instantiatedSignature); } } } return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context)); } } } } return type; } function skippedGenericFunction(node: Node, checkMode: CheckMode) { if (checkMode & CheckMode.Inferential) { // We have skipped a generic function during inferential typing. Obtain the inference context and // indicate this has occurred such that we know a second pass of inference is be needed. const context = getInferenceContext(node)!; context.flags |= InferenceFlags.SkippedGenericFunction; } } function hasInferenceCandidates(info: InferenceInfo) { return !!(info.candidates || info.contraCandidates); } function hasOverlappingInferences(a: InferenceInfo[], b: InferenceInfo[]) { for (let i = 0; i < a.length; i++) { if (hasInferenceCandidates(a[i]) && hasInferenceCandidates(b[i])) { return true; } } return false; } function mergeInferences(target: InferenceInfo[], source: InferenceInfo[]) { for (let i = 0; i < target.length; i++) { if (!hasInferenceCandidates(target[i]) && hasInferenceCandidates(source[i])) { target[i] = source[i]; } } } function getUniqueTypeParameters(context: InferenceContext, typeParameters: readonly TypeParameter[]): readonly TypeParameter[] { const result: TypeParameter[] = []; let oldTypeParameters: TypeParameter[] | undefined; let newTypeParameters: TypeParameter[] | undefined; for (const tp of typeParameters) { const name = tp.symbol.escapedName; if (hasTypeParameterByName(context.inferredTypeParameters, name) || hasTypeParameterByName(result, name)) { const newName = getUniqueTypeParameterName(concatenate(context.inferredTypeParameters, result), name); const symbol = createSymbol(SymbolFlags.TypeParameter, newName); const newTypeParameter = createTypeParameter(symbol); newTypeParameter.target = tp; oldTypeParameters = append(oldTypeParameters, tp); newTypeParameters = append(newTypeParameters, newTypeParameter); result.push(newTypeParameter); } else { result.push(tp); } } if (newTypeParameters) { const mapper = createTypeMapper(oldTypeParameters!, newTypeParameters); for (const tp of newTypeParameters) { tp.mapper = mapper; } } return result; } function hasTypeParameterByName(typeParameters: readonly TypeParameter[] | undefined, name: __String) { return some(typeParameters, tp => tp.symbol.escapedName === name); } function getUniqueTypeParameterName(typeParameters: readonly TypeParameter[], baseName: __String) { let len = (baseName).length; while (len > 1 && (baseName).charCodeAt(len - 1) >= CharacterCodes._0 && (baseName).charCodeAt(len - 1) <= CharacterCodes._9) len--; const s = (baseName).slice(0, len); for (let index = 1; true; index++) { const augmentedName = <__String>(s + index); if (!hasTypeParameterByName(typeParameters, augmentedName)) { return augmentedName; } } } function getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) { const signature = getSingleCallSignature(funcType); if (signature && !signature.typeParameters) { return getReturnTypeOfSignature(signature); } } function getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr: CallChain) { const funcType = checkExpression(expr.expression); const nonOptionalType = getOptionalExpressionType(funcType, expr.expression); const returnType = getReturnTypeOfSingleNonGenericCallSignature(funcType); return returnType && propagateOptionalTypeMarker(returnType, expr, nonOptionalType !== funcType); } /** * Returns the type of an expression. Unlike checkExpression, this function is simply concerned * with computing the type and may not fully check all contained sub-expressions for errors. */ function getTypeOfExpression(node: Expression) { // Don't bother caching types that require no flow analysis and are quick to compute. const quickType = getQuickTypeOfExpression(node); if (quickType) { return quickType; } // If a type has been cached for the node, return it. if (node.flags & NodeFlags.TypeCached && flowTypeCache) { const cachedType = flowTypeCache[getNodeId(node)]; if (cachedType) { return cachedType; } } const startInvocationCount = flowInvocationCount; const type = checkExpression(node); // If control flow analysis was required to determine the type, it is worth caching. if (flowInvocationCount !== startInvocationCount) { const cache = flowTypeCache || (flowTypeCache = []); cache[getNodeId(node)] = type; setNodeFlags(node, node.flags | NodeFlags.TypeCached); } return type; } function getQuickTypeOfExpression(node: Expression) { const expr = skipParentheses(node); // Optimize for the common case of a call to a function with a single non-generic call // signature where we can just fetch the return type without checking the arguments. if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) { const type = isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) : getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression)); if (type) { return type; } } else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) { return getTypeFromTypeNode((expr).type); } else if (node.kind === SyntaxKind.NumericLiteral || node.kind === SyntaxKind.StringLiteral || node.kind === SyntaxKind.TrueKeyword || node.kind === SyntaxKind.FalseKeyword) { return checkExpression(node); } return undefined; } /** * Returns the type of an expression. Unlike checkExpression, this function is simply concerned * with computing the type and may not fully check all contained sub-expressions for errors. * It is intended for uses where you know there is no contextual type, * and requesting the contextual type might cause a circularity or other bad behaviour. * It sets the contextual type of the node to any before calling getTypeOfExpression. */ function getContextFreeTypeOfExpression(node: Expression) { const links = getNodeLinks(node); if (links.contextFreeType) { return links.contextFreeType; } const saveContextualType = node.contextualType; node.contextualType = anyType; try { const type = links.contextFreeType = checkExpression(node, CheckMode.SkipContextSensitive); return type; } finally { // In the event our operation is canceled or some other exception occurs, reset the contextual type // so that we do not accidentally hold onto an instance of the checker, as a Type created in the services layer // may hold onto the checker that created it. node.contextualType = saveContextualType; } } function checkExpression(node: Expression | QualifiedName, checkMode?: CheckMode, forceTuple?: boolean): Type { tracing.push(tracing.Phase.Check, "checkExpression", { kind: node.kind, pos: node.pos, end: node.end }); const saveCurrentNode = currentNode; currentNode = node; instantiationCount = 0; const uninstantiatedType = checkExpressionWorker(node, checkMode, forceTuple); const type = instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); if (isConstEnumObjectType(type)) { checkConstEnumAccess(node, type); } currentNode = saveCurrentNode; tracing.pop(); return type; } function checkConstEnumAccess(node: Expression | QualifiedName, type: Type) { // enum object type for const enums are only permitted in: // - 'left' in property access // - 'object' in indexed access // - target in rhs of import statement const ok = (node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent).expression === node) || (node.parent.kind === SyntaxKind.ElementAccessExpression && (node.parent).expression === node) || ((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(node) || (node.parent.kind === SyntaxKind.TypeQuery && (node.parent).exprName === node)) || (node.parent.kind === SyntaxKind.ExportSpecifier); // We allow reexporting const enums if (!ok) { error(node, Diagnostics.const_enums_can_only_be_used_in_property_or_index_access_expressions_or_the_right_hand_side_of_an_import_declaration_or_export_assignment_or_type_query); } if (compilerOptions.isolatedModules) { Debug.assert(!!(type.symbol.flags & SymbolFlags.ConstEnum)); const constEnumDeclaration = type.symbol.valueDeclaration as EnumDeclaration; if (constEnumDeclaration.flags & NodeFlags.Ambient) { error(node, Diagnostics.Cannot_access_ambient_const_enums_when_the_isolatedModules_flag_is_provided); } } } function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { const tag = isInJSFile(node) ? getJSDocTypeTag(node) : undefined; if (tag) { return checkAssertionWorker(tag, tag.typeExpression.type, node.expression, checkMode); } return checkExpression(node.expression, checkMode); } function checkExpressionWorker(node: Expression | QualifiedName, checkMode: CheckMode | undefined, forceTuple?: boolean): Type { const kind = node.kind; if (cancellationToken) { // Only bother checking on a few construct kinds. We don't want to be excessively // hitting the cancellation token on every node we check. switch (kind) { case SyntaxKind.ClassExpression: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: cancellationToken.throwIfCancellationRequested(); } } switch (kind) { case SyntaxKind.Identifier: return checkIdentifier(node); case SyntaxKind.ThisKeyword: return checkThisExpression(node); case SyntaxKind.SuperKeyword: return checkSuperExpression(node); case SyntaxKind.NullKeyword: return nullWideningType; case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.StringLiteral: return getFreshTypeOfLiteralType(getLiteralType((node as StringLiteralLike).text)); case SyntaxKind.NumericLiteral: checkGrammarNumericLiteral(node as NumericLiteral); return getFreshTypeOfLiteralType(getLiteralType(+(node as NumericLiteral).text)); case SyntaxKind.BigIntLiteral: checkGrammarBigIntLiteral(node as BigIntLiteral); return getFreshTypeOfLiteralType(getBigIntLiteralType(node as BigIntLiteral)); case SyntaxKind.TrueKeyword: return trueType; case SyntaxKind.FalseKeyword: return falseType; case SyntaxKind.TemplateExpression: return checkTemplateExpression(node); case SyntaxKind.RegularExpressionLiteral: return globalRegExpType; case SyntaxKind.ArrayLiteralExpression: return checkArrayLiteral(node, checkMode, forceTuple); case SyntaxKind.ObjectLiteralExpression: return checkObjectLiteral(node, checkMode); case SyntaxKind.PropertyAccessExpression: return checkPropertyAccessExpression(node); case SyntaxKind.QualifiedName: return checkQualifiedName(node); case SyntaxKind.ElementAccessExpression: return checkIndexedAccess(node); case SyntaxKind.CallExpression: if ((node).expression.kind === SyntaxKind.ImportKeyword) { return checkImportCallExpression(node); } // falls through case SyntaxKind.NewExpression: return checkCallExpression(node, checkMode); case SyntaxKind.TaggedTemplateExpression: return checkTaggedTemplateExpression(node); case SyntaxKind.ParenthesizedExpression: return checkParenthesizedExpression(node, checkMode); case SyntaxKind.ClassExpression: return checkClassExpression(node); case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: return checkFunctionExpressionOrObjectLiteralMethod(node, checkMode); case SyntaxKind.TypeOfExpression: return checkTypeOfExpression(node); case SyntaxKind.TypeAssertionExpression: case SyntaxKind.AsExpression: return checkAssertion(node); case SyntaxKind.NonNullExpression: return checkNonNullAssertion(node); case SyntaxKind.MetaProperty: return checkMetaProperty(node); case SyntaxKind.DeleteExpression: return checkDeleteExpression(node); case SyntaxKind.VoidExpression: return checkVoidExpression(node); case SyntaxKind.AwaitExpression: return checkAwaitExpression(node); case SyntaxKind.PrefixUnaryExpression: return checkPrefixUnaryExpression(node); case SyntaxKind.PostfixUnaryExpression: return checkPostfixUnaryExpression(node); case SyntaxKind.BinaryExpression: return checkBinaryExpression(node, checkMode); case SyntaxKind.ConditionalExpression: return checkConditionalExpression(node, checkMode); case SyntaxKind.SpreadElement: return checkSpreadExpression(node, checkMode); case SyntaxKind.OmittedExpression: return undefinedWideningType; case SyntaxKind.YieldExpression: return checkYieldExpression(node); case SyntaxKind.SyntheticExpression: return checkSyntheticExpression(node); case SyntaxKind.JsxExpression: return checkJsxExpression(node, checkMode); case SyntaxKind.JsxElement: return checkJsxElement(node, checkMode); case SyntaxKind.JsxSelfClosingElement: return checkJsxSelfClosingElement(node, checkMode); case SyntaxKind.JsxFragment: return checkJsxFragment(node); case SyntaxKind.JsxAttributes: return checkJsxAttributes(node, checkMode); case SyntaxKind.JsxOpeningElement: Debug.fail("Shouldn't ever directly check a JsxOpeningElement"); } return errorType; } // DECLARATION AND STATEMENT TYPE CHECKING function checkTypeParameter(node: TypeParameterDeclaration) { // Grammar Checking if (node.expression) { grammarErrorOnFirstToken(node.expression, Diagnostics.Type_expected); } checkSourceElement(node.constraint); checkSourceElement(node.default); const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfNode(node)); // Resolve base constraint to reveal circularity errors getBaseConstraintOfType(typeParameter); if (!hasNonCircularTypeParameterDefault(typeParameter)) { error(node.default, Diagnostics.Type_parameter_0_has_a_circular_default, typeToString(typeParameter)); } const constraintType = getConstraintOfTypeParameter(typeParameter); const defaultType = getDefaultFromTypeParameter(typeParameter); if (constraintType && defaultType) { checkTypeAssignableTo(defaultType, getTypeWithThisArgument(instantiateType(constraintType, makeUnaryTypeMapper(typeParameter, defaultType)), defaultType), node.default, Diagnostics.Type_0_does_not_satisfy_the_constraint_1); } if (produceDiagnostics) { checkTypeNameIsReserved(node.name, Diagnostics.Type_parameter_name_cannot_be_0); } } function checkParameter(node: ParameterDeclaration) { // Grammar checking // It is a SyntaxError if the Identifier "eval" or the Identifier "arguments" occurs as the // Identifier in a PropertySetParameterList of a PropertyAssignment that is contained in strict code // or if its FunctionBody is strict code(11.1.5). checkGrammarDecoratorsAndModifiers(node); checkVariableLikeDeclaration(node); const func = getContainingFunction(node)!; if (hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier)) { if (!(func.kind === SyntaxKind.Constructor && nodeIsPresent(func.body))) { error(node, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation); } if (func.kind === SyntaxKind.Constructor && isIdentifier(node.name) && node.name.escapedText === "constructor") { error(node.name, Diagnostics.constructor_cannot_be_used_as_a_parameter_property_name); } } if (node.questionToken && isBindingPattern(node.name) && (func as FunctionLikeDeclaration).body) { error(node, Diagnostics.A_binding_pattern_parameter_cannot_be_optional_in_an_implementation_signature); } if (node.name && isIdentifier(node.name) && (node.name.escapedText === "this" || node.name.escapedText === "new")) { if (func.parameters.indexOf(node) !== 0) { error(node, Diagnostics.A_0_parameter_must_be_the_first_parameter, node.name.escapedText as string); } if (func.kind === SyntaxKind.Constructor || func.kind === SyntaxKind.ConstructSignature || func.kind === SyntaxKind.ConstructorType) { error(node, Diagnostics.A_constructor_cannot_have_a_this_parameter); } if (func.kind === SyntaxKind.ArrowFunction) { error(node, Diagnostics.An_arrow_function_cannot_have_a_this_parameter); } if (func.kind === SyntaxKind.GetAccessor || func.kind === SyntaxKind.SetAccessor) { error(node, Diagnostics.get_and_set_accessors_cannot_declare_this_parameters); } } // Only check rest parameter type if it's not a binding pattern. Since binding patterns are // not allowed in a rest parameter, we already have an error from checkGrammarParameterList. if (node.dotDotDotToken && !isBindingPattern(node.name) && !isTypeAssignableTo(getReducedType(getTypeOfSymbol(node.symbol)), anyReadonlyArrayType)) { error(node, Diagnostics.A_rest_parameter_must_be_of_an_array_type); } } function checkTypePredicate(node: TypePredicateNode): void { const parent = getTypePredicateParent(node); if (!parent) { // The parent must not be valid. error(node, Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods); return; } const signature = getSignatureFromDeclaration(parent); const typePredicate = getTypePredicateOfSignature(signature); if (!typePredicate) { return; } checkSourceElement(node.type); const { parameterName } = node; if (typePredicate.kind === TypePredicateKind.This || typePredicate.kind === TypePredicateKind.AssertsThis) { getTypeFromThisTypeNode(parameterName as ThisTypeNode); } else { if (typePredicate.parameterIndex >= 0) { if (signatureHasRestParameter(signature) && typePredicate.parameterIndex === signature.parameters.length - 1) { error(parameterName, Diagnostics.A_type_predicate_cannot_reference_a_rest_parameter); } else { if (typePredicate.type) { const leadingError = () => chainDiagnosticMessages(/*details*/ undefined, Diagnostics.A_type_predicate_s_type_must_be_assignable_to_its_parameter_s_type); checkTypeAssignableTo(typePredicate.type, getTypeOfSymbol(signature.parameters[typePredicate.parameterIndex]), node.type, /*headMessage*/ undefined, leadingError); } } } else if (parameterName) { let hasReportedError = false; for (const { name } of parent.parameters) { if (isBindingPattern(name) && checkIfTypePredicateVariableIsDeclaredInBindingPattern(name, parameterName, typePredicate.parameterName)) { hasReportedError = true; break; } } if (!hasReportedError) { error(node.parameterName, Diagnostics.Cannot_find_parameter_0, typePredicate.parameterName); } } } } function getTypePredicateParent(node: Node): SignatureDeclaration | undefined { switch (node.parent.kind) { case SyntaxKind.ArrowFunction: case SyntaxKind.CallSignature: case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.FunctionType: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: const parent = node.parent; if (node === parent.type) { return parent; } } } function checkIfTypePredicateVariableIsDeclaredInBindingPattern( pattern: BindingPattern, predicateVariableNode: Node, predicateVariableName: string) { for (const element of pattern.elements) { if (isOmittedExpression(element)) { continue; } const name = element.name; if (name.kind === SyntaxKind.Identifier && name.escapedText === predicateVariableName) { error(predicateVariableNode, Diagnostics.A_type_predicate_cannot_reference_element_0_in_a_binding_pattern, predicateVariableName); return true; } else if (name.kind === SyntaxKind.ArrayBindingPattern || name.kind === SyntaxKind.ObjectBindingPattern) { if (checkIfTypePredicateVariableIsDeclaredInBindingPattern( name, predicateVariableNode, predicateVariableName)) { return true; } } } } function checkSignatureDeclaration(node: SignatureDeclaration) { // Grammar checking if (node.kind === SyntaxKind.IndexSignature) { checkGrammarIndexSignature(node); } // TODO (yuisu): Remove this check in else-if when SyntaxKind.Construct is moved and ambient context is handled else if (node.kind === SyntaxKind.FunctionType || node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.ConstructorType || node.kind === SyntaxKind.CallSignature || node.kind === SyntaxKind.Constructor || node.kind === SyntaxKind.ConstructSignature) { checkGrammarFunctionLikeDeclaration(node); } const functionFlags = getFunctionFlags(node); if (!(functionFlags & FunctionFlags.Invalid)) { // Async generators prior to ESNext require the __await and __asyncGenerator helpers if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.AsyncGenerator && languageVersion < ScriptTarget.ESNext) { checkExternalEmitHelpers(node, ExternalEmitHelpers.AsyncGeneratorIncludes); } // Async functions prior to ES2017 require the __awaiter helper if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async && languageVersion < ScriptTarget.ES2017) { checkExternalEmitHelpers(node, ExternalEmitHelpers.Awaiter); } // Generator functions, Async functions, and Async Generator functions prior to // ES2015 require the __generator helper if ((functionFlags & FunctionFlags.AsyncGenerator) !== FunctionFlags.Normal && languageVersion < ScriptTarget.ES2015) { checkExternalEmitHelpers(node, ExternalEmitHelpers.Generator); } } checkTypeParameters(node.typeParameters); forEach(node.parameters, checkParameter); // TODO(rbuckton): Should we start checking JSDoc types? if (node.type) { checkSourceElement(node.type); } if (produceDiagnostics) { checkCollisionWithArgumentsInGeneratedCode(node); const returnTypeNode = getEffectiveReturnTypeNode(node); if (noImplicitAny && !returnTypeNode) { switch (node.kind) { case SyntaxKind.ConstructSignature: error(node, Diagnostics.Construct_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); break; case SyntaxKind.CallSignature: error(node, Diagnostics.Call_signature_which_lacks_return_type_annotation_implicitly_has_an_any_return_type); break; } } if (returnTypeNode) { const functionFlags = getFunctionFlags(node); if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Generator)) === FunctionFlags.Generator) { const returnType = getTypeFromTypeNode(returnTypeNode); if (returnType === voidType) { error(returnTypeNode, Diagnostics.A_generator_cannot_have_a_void_type_annotation); } else { // Naively, one could check that Generator is assignable to the return type annotation. // However, that would not catch the error in the following case. // // interface BadGenerator extends Iterable, Iterator { } // function* g(): BadGenerator { } // Iterable and Iterator have different types! // const generatorYieldType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Yield, returnType, (functionFlags & FunctionFlags.Async) !== 0) || anyType; const generatorReturnType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, (functionFlags & FunctionFlags.Async) !== 0) || generatorYieldType; const generatorNextType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Next, returnType, (functionFlags & FunctionFlags.Async) !== 0) || unknownType; const generatorInstantiation = createGeneratorReturnType(generatorYieldType, generatorReturnType, generatorNextType, !!(functionFlags & FunctionFlags.Async)); checkTypeAssignableTo(generatorInstantiation, returnType, returnTypeNode); } } else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { checkAsyncFunctionReturnType(node, returnTypeNode); } } if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) { registerForUnusedIdentifiersCheck(node); } } } function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) { const instanceNames = new Map<__String, DeclarationMeaning>(); const staticNames = new Map<__String, DeclarationMeaning>(); // instance and static private identifiers share the same scope const privateIdentifiers = new Map<__String, DeclarationMeaning>(); for (const member of node.members) { if (member.kind === SyntaxKind.Constructor) { for (const param of (member as ConstructorDeclaration).parameters) { if (isParameterPropertyDeclaration(param, member) && !isBindingPattern(param.name)) { addName(instanceNames, param.name, param.name.escapedText, DeclarationMeaning.GetOrSetAccessor); } } } else { const isStatic = hasSyntacticModifier(member, ModifierFlags.Static); const name = member.name; if (!name) { return; } const names = isPrivateIdentifier(name) ? privateIdentifiers : isStatic ? staticNames : instanceNames; const memberName = name && getPropertyNameForPropertyNameNode(name); if (memberName) { switch (member.kind) { case SyntaxKind.GetAccessor: addName(names, name, memberName, DeclarationMeaning.GetAccessor); break; case SyntaxKind.SetAccessor: addName(names, name, memberName, DeclarationMeaning.SetAccessor); break; case SyntaxKind.PropertyDeclaration: addName(names, name, memberName, DeclarationMeaning.GetOrSetAccessor); break; case SyntaxKind.MethodDeclaration: addName(names, name, memberName, DeclarationMeaning.Method); break; } } } } function addName(names: UnderscoreEscapedMap, location: Node, name: __String, meaning: DeclarationMeaning) { const prev = names.get(name); if (prev) { if (prev & DeclarationMeaning.Method) { if (meaning !== DeclarationMeaning.Method) { error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); } } else if (prev & meaning) { error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); } else { names.set(name, prev | meaning); } } else { names.set(name, meaning); } } } /** * Static members being set on a constructor function may conflict with built-in properties * of Function. Esp. in ECMAScript 5 there are non-configurable and non-writable * built-in properties. This check issues a transpile error when a class has a static * member with the same name as a non-writable built-in property. * * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.3 * @see http://www.ecma-international.org/ecma-262/5.1/#sec-15.3.5 * @see http://www.ecma-international.org/ecma-262/6.0/#sec-properties-of-the-function-constructor * @see http://www.ecma-international.org/ecma-262/6.0/#sec-function-instances */ function checkClassForStaticPropertyNameConflicts(node: ClassLikeDeclaration) { for (const member of node.members) { const memberNameNode = member.name; const isStatic = hasSyntacticModifier(member, ModifierFlags.Static); if (isStatic && memberNameNode) { const memberName = getPropertyNameForPropertyNameNode(memberNameNode); switch (memberName) { case "name": case "length": case "caller": case "arguments": case "prototype": const message = Diagnostics.Static_property_0_conflicts_with_built_in_property_Function_0_of_constructor_function_1; const className = getNameOfSymbolAsWritten(getSymbolOfNode(node)); error(memberNameNode, message, memberName, className); break; } } } } function checkObjectTypeForDuplicateDeclarations(node: TypeLiteralNode | InterfaceDeclaration) { const names = new Map(); for (const member of node.members) { if (member.kind === SyntaxKind.PropertySignature) { let memberName: string; const name = member.name!; switch (name.kind) { case SyntaxKind.StringLiteral: case SyntaxKind.NumericLiteral: memberName = name.text; break; case SyntaxKind.Identifier: memberName = idText(name); break; default: continue; } if (names.get(memberName)) { error(getNameOfDeclaration(member.symbol.valueDeclaration), Diagnostics.Duplicate_identifier_0, memberName); error(member.name, Diagnostics.Duplicate_identifier_0, memberName); } else { names.set(memberName, true); } } } } function checkTypeForDuplicateIndexSignatures(node: Node) { if (node.kind === SyntaxKind.InterfaceDeclaration) { const nodeSymbol = getSymbolOfNode(node as InterfaceDeclaration); // in case of merging interface declaration it is possible that we'll enter this check procedure several times for every declaration // to prevent this run check only for the first declaration of a given kind if (nodeSymbol.declarations.length > 0 && nodeSymbol.declarations[0] !== node) { return; } } // TypeScript 1.0 spec (April 2014) // 3.7.4: An object type can contain at most one string index signature and one numeric index signature. // 8.5: A class declaration can have at most one string index member declaration and one numeric index member declaration const indexSymbol = getIndexSymbol(getSymbolOfNode(node)!); if (indexSymbol) { let seenNumericIndexer = false; let seenStringIndexer = false; for (const decl of indexSymbol.declarations) { const declaration = decl; if (declaration.parameters.length === 1 && declaration.parameters[0].type) { switch (declaration.parameters[0].type.kind) { case SyntaxKind.StringKeyword: if (!seenStringIndexer) { seenStringIndexer = true; } else { error(declaration, Diagnostics.Duplicate_string_index_signature); } break; case SyntaxKind.NumberKeyword: if (!seenNumericIndexer) { seenNumericIndexer = true; } else { error(declaration, Diagnostics.Duplicate_number_index_signature); } break; } } } } } function checkPropertyDeclaration(node: PropertyDeclaration | PropertySignature) { // Grammar checking if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarProperty(node)) checkGrammarComputedPropertyName(node.name); checkVariableLikeDeclaration(node); // Private class fields transformation relies on WeakMaps. if (isPrivateIdentifier(node.name) && languageVersion < ScriptTarget.ESNext) { for (let lexicalScope = getEnclosingBlockScopeContainer(node); !!lexicalScope; lexicalScope = getEnclosingBlockScopeContainer(lexicalScope)) { getNodeLinks(lexicalScope).flags |= NodeCheckFlags.ContainsClassWithPrivateIdentifiers; } } } function checkPropertySignature(node: PropertySignature) { if (isPrivateIdentifier(node.name)) { error(node, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); } return checkPropertyDeclaration(node); } function checkMethodDeclaration(node: MethodDeclaration | MethodSignature) { // Grammar checking if (!checkGrammarMethod(node)) checkGrammarComputedPropertyName(node.name); if (isPrivateIdentifier(node.name)) { error(node, Diagnostics.A_method_cannot_be_named_with_a_private_identifier); } // Grammar checking for modifiers is done inside the function checkGrammarFunctionLikeDeclaration checkFunctionOrMethodDeclaration(node); // Abstract methods cannot have an implementation. // Extra checks are to avoid reporting multiple errors relating to the "abstractness" of the node. if (hasSyntacticModifier(node, ModifierFlags.Abstract) && node.kind === SyntaxKind.MethodDeclaration && node.body) { error(node, Diagnostics.Method_0_cannot_have_an_implementation_because_it_is_marked_abstract, declarationNameToString(node.name)); } } function checkConstructorDeclaration(node: ConstructorDeclaration) { // Grammar check on signature of constructor and modifier of the constructor is done in checkSignatureDeclaration function. checkSignatureDeclaration(node); // Grammar check for checking only related to constructorDeclaration if (!checkGrammarConstructorTypeParameters(node)) checkGrammarConstructorTypeAnnotation(node); checkSourceElement(node.body); const symbol = getSymbolOfNode(node); const firstDeclaration = getDeclarationOfKind(symbol, node.kind); // Only type check the symbol once if (node === firstDeclaration) { checkFunctionOrConstructorSymbol(symbol); } // exit early in the case of signature - super checks are not relevant to them if (nodeIsMissing(node.body)) { return; } if (!produceDiagnostics) { return; } function isInstancePropertyWithInitializerOrPrivateIdentifierProperty(n: Node): boolean { if (isPrivateIdentifierPropertyDeclaration(n)) { return true; } return n.kind === SyntaxKind.PropertyDeclaration && !hasSyntacticModifier(n, ModifierFlags.Static) && !!(n).initializer; } // TS 1.0 spec (April 2014): 8.3.2 // Constructors of classes with no extends clause may not contain super calls, whereas // constructors of derived classes must contain at least one super call somewhere in their function body. const containingClassDecl = node.parent; if (getClassExtendsHeritageElement(containingClassDecl)) { captureLexicalThis(node.parent, containingClassDecl); const classExtendsNull = classDeclarationExtendsNull(containingClassDecl); const superCall = findFirstSuperCall(node.body!); if (superCall) { if (classExtendsNull) { error(superCall, Diagnostics.A_constructor_cannot_contain_a_super_call_when_its_class_extends_null); } // The first statement in the body of a constructor (excluding prologue directives) must be a super call // if both of the following are true: // - The containing class is a derived class. // - The constructor declares parameter properties // or the containing class declares instance member variables with initializers. const superCallShouldBeFirst = (compilerOptions.target !== ScriptTarget.ESNext || !compilerOptions.useDefineForClassFields) && (some((node.parent).members, isInstancePropertyWithInitializerOrPrivateIdentifierProperty) || some(node.parameters, p => hasSyntacticModifier(p, ModifierFlags.ParameterPropertyModifier))); // Skip past any prologue directives to find the first statement // to ensure that it was a super call. if (superCallShouldBeFirst) { const statements = node.body!.statements; let superCallStatement: ExpressionStatement | undefined; for (const statement of statements) { if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCall((statement).expression)) { superCallStatement = statement; break; } if (!isPrologueDirective(statement)) { break; } } if (!superCallStatement) { error(node, Diagnostics.A_super_call_must_be_the_first_statement_in_the_constructor_when_a_class_contains_initialized_properties_parameter_properties_or_private_identifiers); } } } else if (!classExtendsNull) { error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call); } } } function checkAccessorDeclaration(node: AccessorDeclaration) { if (produceDiagnostics) { // Grammar checking accessors if (!checkGrammarFunctionLikeDeclaration(node) && !checkGrammarAccessor(node)) checkGrammarComputedPropertyName(node.name); checkDecorators(node); checkSignatureDeclaration(node); if (node.kind === SyntaxKind.GetAccessor) { if (!(node.flags & NodeFlags.Ambient) && nodeIsPresent(node.body) && (node.flags & NodeFlags.HasImplicitReturn)) { if (!(node.flags & NodeFlags.HasExplicitReturn)) { error(node.name, Diagnostics.A_get_accessor_must_return_a_value); } } } // Do not use hasDynamicName here, because that returns false for well known symbols. // We want to perform checkComputedPropertyName for all computed properties, including // well known symbols. if (node.name.kind === SyntaxKind.ComputedPropertyName) { checkComputedPropertyName(node.name); } if (isPrivateIdentifier(node.name)) { error(node.name, Diagnostics.An_accessor_cannot_be_named_with_a_private_identifier); } if (!hasNonBindableDynamicName(node)) { // TypeScript 1.0 spec (April 2014): 8.4.3 // Accessors for the same member name must specify the same accessibility. const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; const otherAccessor = getDeclarationOfKind(getSymbolOfNode(node), otherKind); if (otherAccessor) { const nodeFlags = getEffectiveModifierFlags(node); const otherFlags = getEffectiveModifierFlags(otherAccessor); if ((nodeFlags & ModifierFlags.AccessibilityModifier) !== (otherFlags & ModifierFlags.AccessibilityModifier)) { error(node.name, Diagnostics.Getter_and_setter_accessors_do_not_agree_in_visibility); } if ((nodeFlags & ModifierFlags.Abstract) !== (otherFlags & ModifierFlags.Abstract)) { error(node.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); } // TypeScript 1.0 spec (April 2014): 4.5 // If both accessors include type annotations, the specified types must be identical. checkAccessorDeclarationTypesIdentical(node, otherAccessor, getAnnotatedAccessorType, Diagnostics.get_and_set_accessor_must_have_the_same_type); checkAccessorDeclarationTypesIdentical(node, otherAccessor, getThisTypeOfDeclaration, Diagnostics.get_and_set_accessor_must_have_the_same_this_type); } } const returnType = getTypeOfAccessors(getSymbolOfNode(node)); if (node.kind === SyntaxKind.GetAccessor) { checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); } } checkSourceElement(node.body); } function checkAccessorDeclarationTypesIdentical(first: AccessorDeclaration, second: AccessorDeclaration, getAnnotatedType: (a: AccessorDeclaration) => Type | undefined, message: DiagnosticMessage) { const firstType = getAnnotatedType(first); const secondType = getAnnotatedType(second); if (firstType && secondType && !isTypeIdenticalTo(firstType, secondType)) { error(first, message); } } function checkMissingDeclaration(node: Node) { checkDecorators(node); } function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] { return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters, getMinTypeArgumentCount(typeParameters), isInJSFile(node)); } function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): boolean { let typeArguments: Type[] | undefined; let mapper: TypeMapper | undefined; let result = true; for (let i = 0; i < typeParameters.length; i++) { const constraint = getConstraintOfTypeParameter(typeParameters[i]); if (constraint) { if (!typeArguments) { typeArguments = getEffectiveTypeArguments(node, typeParameters); mapper = createTypeMapper(typeParameters, typeArguments); } result = result && checkTypeAssignableTo( typeArguments[i], instantiateType(constraint, mapper), node.typeArguments![i], Diagnostics.Type_0_does_not_satisfy_the_constraint_1); } } return result; } function getTypeParametersForTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments) { const type = getTypeFromTypeReference(node); if (type !== errorType) { const symbol = getNodeLinks(node).resolvedSymbol; if (symbol) { return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters || (getObjectFlags(type) & ObjectFlags.Reference ? (type).target.localTypeParameters : undefined); } } return undefined; } function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) { checkGrammarTypeArguments(node, node.typeArguments); if (node.kind === SyntaxKind.TypeReference && node.typeName.jsdocDotPos !== undefined && !isInJSFile(node) && !isInJSDoc(node)) { grammarErrorAtPos(node, node.typeName.jsdocDotPos, 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); } forEach(node.typeArguments, checkSourceElement); const type = getTypeFromTypeReference(node); if (type !== errorType) { if (node.typeArguments && produceDiagnostics) { const typeParameters = getTypeParametersForTypeReference(node); if (typeParameters) { checkTypeArgumentConstraints(node, typeParameters); } } const symbol = getNodeLinks(node).resolvedSymbol; if (symbol) { if (some(symbol.declarations, d => isTypeDeclaration(d) && !!(d.flags & NodeFlags.Deprecated))) { errorOrSuggestion(/* isError */ false, getDeprecatedSuggestionNode(node), Diagnostics._0_is_deprecated, symbol.escapedName as string); } if (type.flags & TypeFlags.Enum && symbol.flags & SymbolFlags.EnumMember) { error(node, Diagnostics.Enum_type_0_has_members_with_initializers_that_are_not_literals, typeToString(type)); } } } } function getTypeArgumentConstraint(node: TypeNode): Type | undefined { const typeReferenceNode = tryCast(node.parent, isTypeReferenceType); if (!typeReferenceNode) return undefined; const typeParameters = getTypeParametersForTypeReference(typeReferenceNode)!; // TODO: GH#18217 const constraint = getConstraintOfTypeParameter(typeParameters[typeReferenceNode.typeArguments!.indexOf(node)]); return constraint && instantiateType(constraint, createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReferenceNode, typeParameters))); } function checkTypeQuery(node: TypeQueryNode) { getTypeFromTypeQueryNode(node); } function checkTypeLiteral(node: TypeLiteralNode) { forEach(node.members, checkSourceElement); if (produceDiagnostics) { const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); checkIndexConstraints(type); checkTypeForDuplicateIndexSignatures(node); checkObjectTypeForDuplicateDeclarations(node); } } function checkArrayType(node: ArrayTypeNode) { checkSourceElement(node.elementType); } function checkTupleType(node: TupleTypeNode) { const elementTypes = node.elements; let seenOptionalElement = false; let seenRestElement = false; const hasNamedElement = some(elementTypes, isNamedTupleMember); for (let i = 0; i < elementTypes.length; i++) { const e = elementTypes[i]; if (e.kind !== SyntaxKind.NamedTupleMember && hasNamedElement) { grammarErrorOnNode(e, Diagnostics.Tuple_members_must_all_have_names_or_all_not_have_names); break; } const flags = getTupleElementFlags(e); if (flags & ElementFlags.Variadic) { const type = getTypeFromTypeNode((e).type); if (!isArrayLikeType(type)) { error(e, Diagnostics.A_rest_element_type_must_be_an_array_type); break; } if (isArrayType(type) || isTupleType(type) && type.target.combinedFlags & ElementFlags.Rest) { seenRestElement = true; } } else if (flags & ElementFlags.Rest) { seenRestElement = true; } else if (flags & ElementFlags.Optional) { seenOptionalElement = true; } else if (seenOptionalElement) { grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element); break; } if (seenRestElement && i !== elementTypes.length - 1) { grammarErrorOnNode(e, Diagnostics.A_rest_element_must_be_last_in_a_tuple_type); break; } } forEach(node.elements, checkSourceElement); getTypeFromTypeNode(node); } function checkUnionOrIntersectionType(node: UnionOrIntersectionTypeNode) { forEach(node.types, checkSourceElement); getTypeFromTypeNode(node); } function checkIndexedAccessIndexType(type: Type, accessNode: IndexedAccessTypeNode | ElementAccessExpression) { if (!(type.flags & TypeFlags.IndexedAccess)) { return type; } // Check if the index type is assignable to 'keyof T' for the object type. const objectType = (type).objectType; const indexType = (type).indexType; if (isTypeAssignableTo(indexType, getIndexType(objectType, /*stringsOnly*/ false))) { if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(objectType) & MappedTypeModifiers.IncludeReadonly) { error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); } return type; } // Check if we're indexing with a numeric type and if either object or index types // is a generic type with a constraint that has a numeric index signature. const apparentObjectType = getApparentType(objectType); if (getIndexInfoOfType(apparentObjectType, IndexKind.Number) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { return type; } if (isGenericObjectType(objectType)) { const propertyName = getPropertyNameFromIndex(indexType, accessNode); if (propertyName) { const propertySymbol = forEachType(apparentObjectType, t => getPropertyOfType(t, propertyName)); if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) { error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName)); return errorType; } } } error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType)); return errorType; } function checkIndexedAccessType(node: IndexedAccessTypeNode) { checkSourceElement(node.objectType); checkSourceElement(node.indexType); checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node); } function checkMappedType(node: MappedTypeNode) { checkSourceElement(node.typeParameter); checkSourceElement(node.nameType); checkSourceElement(node.type); if (!node.type) { reportImplicitAny(node, anyType); } const type = getTypeFromMappedTypeNode(node); const nameType = getNameTypeFromMappedType(type); if (nameType) { checkTypeAssignableTo(nameType, keyofConstraintType, node.nameType); } else { const constraintType = getConstraintTypeFromMappedType(type); checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); } } function checkThisType(node: ThisTypeNode) { getTypeFromThisTypeNode(node); } function checkTypeOperator(node: TypeOperatorNode) { checkGrammarTypeOperatorNode(node); checkSourceElement(node.type); } function checkConditionalType(node: ConditionalTypeNode) { forEachChild(node, checkSourceElement); } function checkInferType(node: InferTypeNode) { if (!findAncestor(node, n => n.parent && n.parent.kind === SyntaxKind.ConditionalType && (n.parent).extendsType === n)) { grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type); } checkSourceElement(node.typeParameter); registerForUnusedIdentifiersCheck(node); } function checkTemplateLiteralType(node: TemplateLiteralTypeNode) { for (const span of node.templateSpans) { checkSourceElement(span.type); const type = getTypeFromTypeNode(span.type); checkTypeAssignableTo(type, templateConstraintType, span.type); } getTypeFromTypeNode(node); } function checkImportType(node: ImportTypeNode) { checkSourceElement(node.argument); getTypeFromTypeNode(node); } function checkNamedTupleMember(node: NamedTupleMember) { if (node.dotDotDotToken && node.questionToken) { grammarErrorOnNode(node, Diagnostics.A_tuple_member_cannot_be_both_optional_and_rest); } if (node.type.kind === SyntaxKind.OptionalType) { grammarErrorOnNode(node.type, Diagnostics.A_labeled_tuple_element_is_declared_as_optional_with_a_question_mark_after_the_name_and_before_the_colon_rather_than_after_the_type); } if (node.type.kind === SyntaxKind.RestType) { grammarErrorOnNode(node.type, Diagnostics.A_labeled_tuple_element_is_declared_as_rest_with_a_before_the_name_rather_than_before_the_type); } checkSourceElement(node.type); getTypeFromTypeNode(node); } function isPrivateWithinAmbient(node: Node): boolean { return (hasEffectiveModifier(node, ModifierFlags.Private) || isPrivateIdentifierPropertyDeclaration(node)) && !!(node.flags & NodeFlags.Ambient); } function getEffectiveDeclarationFlags(n: Declaration, flagsToCheck: ModifierFlags): ModifierFlags { let flags = getCombinedModifierFlags(n); // children of classes (even ambient classes) should not be marked as ambient or export // because those flags have no useful semantics there. if (n.parent.kind !== SyntaxKind.InterfaceDeclaration && n.parent.kind !== SyntaxKind.ClassDeclaration && n.parent.kind !== SyntaxKind.ClassExpression && n.flags & NodeFlags.Ambient) { if (!(flags & ModifierFlags.Ambient) && !(isModuleBlock(n.parent) && isModuleDeclaration(n.parent.parent) && isGlobalScopeAugmentation(n.parent.parent))) { // It is nested in an ambient context, which means it is automatically exported flags |= ModifierFlags.Export; } flags |= ModifierFlags.Ambient; } return flags & flagsToCheck; } function checkFunctionOrConstructorSymbol(symbol: Symbol): void { if (!produceDiagnostics) { return; } function getCanonicalOverload(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined): Declaration { // Consider the canonical set of flags to be the flags of the bodyDeclaration or the first declaration // Error on all deviations from this canonical set of flags // The caveat is that if some overloads are defined in lib.d.ts, we don't want to // report the errors on those. To achieve this, we will say that the implementation is // the canonical signature only if it is in the same container as the first overload const implementationSharesContainerWithFirstOverload = implementation !== undefined && implementation.parent === overloads[0].parent; return implementationSharesContainerWithFirstOverload ? implementation! : overloads[0]; } function checkFlagAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined, flagsToCheck: ModifierFlags, someOverloadFlags: ModifierFlags, allOverloadFlags: ModifierFlags): void { // Error if some overloads have a flag that is not shared by all overloads. To find the // deviations, we XOR someOverloadFlags with allOverloadFlags const someButNotAllOverloadFlags = someOverloadFlags ^ allOverloadFlags; if (someButNotAllOverloadFlags !== 0) { const canonicalFlags = getEffectiveDeclarationFlags(getCanonicalOverload(overloads, implementation), flagsToCheck); forEach(overloads, o => { const deviation = getEffectiveDeclarationFlags(o, flagsToCheck) ^ canonicalFlags; if (deviation & ModifierFlags.Export) { error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_exported_or_non_exported); } else if (deviation & ModifierFlags.Ambient) { error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_ambient_or_non_ambient); } else if (deviation & (ModifierFlags.Private | ModifierFlags.Protected)) { error(getNameOfDeclaration(o) || o, Diagnostics.Overload_signatures_must_all_be_public_private_or_protected); } else if (deviation & ModifierFlags.Abstract) { error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_abstract_or_non_abstract); } }); } } function checkQuestionTokenAgreementBetweenOverloads(overloads: Declaration[], implementation: FunctionLikeDeclaration | undefined, someHaveQuestionToken: boolean, allHaveQuestionToken: boolean): void { if (someHaveQuestionToken !== allHaveQuestionToken) { const canonicalHasQuestionToken = hasQuestionToken(getCanonicalOverload(overloads, implementation)); forEach(overloads, o => { const deviation = hasQuestionToken(o) !== canonicalHasQuestionToken; if (deviation) { error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_optional_or_required); } }); } } const flagsToCheck: ModifierFlags = ModifierFlags.Export | ModifierFlags.Ambient | ModifierFlags.Private | ModifierFlags.Protected | ModifierFlags.Abstract; let someNodeFlags: ModifierFlags = ModifierFlags.None; let allNodeFlags = flagsToCheck; let someHaveQuestionToken = false; let allHaveQuestionToken = true; let hasOverloads = false; let bodyDeclaration: FunctionLikeDeclaration | undefined; let lastSeenNonAmbientDeclaration: FunctionLikeDeclaration | undefined; let previousDeclaration: SignatureDeclaration | undefined; const declarations = symbol.declarations; const isConstructor = (symbol.flags & SymbolFlags.Constructor) !== 0; function reportImplementationExpectedError(node: SignatureDeclaration): void { if (node.name && nodeIsMissing(node.name)) { return; } let seen = false; const subsequentNode = forEachChild(node.parent, c => { if (seen) { return c; } else { seen = c === node; } }); // We may be here because of some extra nodes between overloads that could not be parsed into a valid node. // In this case the subsequent node is not really consecutive (.pos !== node.end), and we must ignore it here. if (subsequentNode && subsequentNode.pos === node.end) { if (subsequentNode.kind === node.kind) { const errorNode: Node = (subsequentNode).name || subsequentNode; const subsequentName = (subsequentNode).name; if (node.name && subsequentName && ( // both are private identifiers isPrivateIdentifier(node.name) && isPrivateIdentifier(subsequentName) && node.name.escapedText === subsequentName.escapedText || // Both are computed property names // TODO: GH#17345: These are methods, so handle computed name case. (`Always allowing computed property names is *not* the correct behavior!) isComputedPropertyName(node.name) && isComputedPropertyName(subsequentName) || // Both are literal property names that are the same. isPropertyNameLiteral(node.name) && isPropertyNameLiteral(subsequentName) && getEscapedTextOfIdentifierOrLiteral(node.name) === getEscapedTextOfIdentifierOrLiteral(subsequentName) )) { const reportError = (node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature) && hasSyntacticModifier(node, ModifierFlags.Static) !== hasSyntacticModifier(subsequentNode, ModifierFlags.Static); // we can get here in two cases // 1. mixed static and instance class members // 2. something with the same name was defined before the set of overloads that prevents them from merging // here we'll report error only for the first case since for second we should already report error in binder if (reportError) { const diagnostic = hasSyntacticModifier(node, ModifierFlags.Static) ? Diagnostics.Function_overload_must_be_static : Diagnostics.Function_overload_must_not_be_static; error(errorNode, diagnostic); } return; } if (nodeIsPresent((subsequentNode).body)) { error(errorNode, Diagnostics.Function_implementation_name_must_be_0, declarationNameToString(node.name)); return; } } } const errorNode: Node = node.name || node; if (isConstructor) { error(errorNode, Diagnostics.Constructor_implementation_is_missing); } else { // Report different errors regarding non-consecutive blocks of declarations depending on whether // the node in question is abstract. if (hasSyntacticModifier(node, ModifierFlags.Abstract)) { error(errorNode, Diagnostics.All_declarations_of_an_abstract_method_must_be_consecutive); } else { error(errorNode, Diagnostics.Function_implementation_is_missing_or_not_immediately_following_the_declaration); } } } let duplicateFunctionDeclaration = false; let multipleConstructorImplementation = false; let hasNonAmbientClass = false; const functionDeclarations = [] as Declaration[]; for (const current of declarations) { const node = current; const inAmbientContext = node.flags & NodeFlags.Ambient; const inAmbientContextOrInterface = node.parent && (node.parent.kind === SyntaxKind.InterfaceDeclaration || node.parent.kind === SyntaxKind.TypeLiteral) || inAmbientContext; if (inAmbientContextOrInterface) { // check if declarations are consecutive only if they are non-ambient // 1. ambient declarations can be interleaved // i.e. this is legal // declare function foo(); // declare function bar(); // declare function foo(); // 2. mixing ambient and non-ambient declarations is a separate error that will be reported - do not want to report an extra one previousDeclaration = undefined; } if ((node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression) && !inAmbientContext) { hasNonAmbientClass = true; } if (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature || node.kind === SyntaxKind.Constructor) { functionDeclarations.push(node); const currentNodeFlags = getEffectiveDeclarationFlags(node, flagsToCheck); someNodeFlags |= currentNodeFlags; allNodeFlags &= currentNodeFlags; someHaveQuestionToken = someHaveQuestionToken || hasQuestionToken(node); allHaveQuestionToken = allHaveQuestionToken && hasQuestionToken(node); const bodyIsPresent = nodeIsPresent((node as FunctionLikeDeclaration).body); if (bodyIsPresent && bodyDeclaration) { if (isConstructor) { multipleConstructorImplementation = true; } else { duplicateFunctionDeclaration = true; } } else if (previousDeclaration?.parent === node.parent && previousDeclaration.end !== node.pos) { reportImplementationExpectedError(previousDeclaration); } if (bodyIsPresent) { if (!bodyDeclaration) { bodyDeclaration = node as FunctionLikeDeclaration; } } else { hasOverloads = true; } previousDeclaration = node; if (!inAmbientContextOrInterface) { lastSeenNonAmbientDeclaration = node as FunctionLikeDeclaration; } } } if (multipleConstructorImplementation) { forEach(functionDeclarations, declaration => { error(declaration, Diagnostics.Multiple_constructor_implementations_are_not_allowed); }); } if (duplicateFunctionDeclaration) { forEach(functionDeclarations, declaration => { error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Duplicate_function_implementation); }); } if (hasNonAmbientClass && !isConstructor && symbol.flags & SymbolFlags.Function) { // A non-ambient class cannot be an implementation for a non-constructor function/class merge // TODO: The below just replicates our older error from when classes and functions were // entirely unable to merge - a more helpful message like "Class declaration cannot implement overload list" // might be warranted. :shrug: forEach(declarations, declaration => { addDuplicateDeclarationError(declaration, Diagnostics.Duplicate_identifier_0, symbolName(symbol), declarations); }); } // Abstract methods can't have an implementation -- in particular, they don't need one. if (lastSeenNonAmbientDeclaration && !lastSeenNonAmbientDeclaration.body && !hasSyntacticModifier(lastSeenNonAmbientDeclaration, ModifierFlags.Abstract) && !lastSeenNonAmbientDeclaration.questionToken) { reportImplementationExpectedError(lastSeenNonAmbientDeclaration); } if (hasOverloads) { checkFlagAgreementBetweenOverloads(declarations, bodyDeclaration, flagsToCheck, someNodeFlags, allNodeFlags); checkQuestionTokenAgreementBetweenOverloads(declarations, bodyDeclaration, someHaveQuestionToken, allHaveQuestionToken); if (bodyDeclaration) { const signatures = getSignaturesOfSymbol(symbol); const bodySignature = getSignatureFromDeclaration(bodyDeclaration); for (const signature of signatures) { if (!isImplementationCompatibleWithOverload(bodySignature, signature)) { addRelatedInfo( error(signature.declaration, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature), createDiagnosticForNode(bodyDeclaration, Diagnostics.The_implementation_signature_is_declared_here) ); break; } } } } } function checkExportsOnMergedDeclarations(node: Declaration): void { if (!produceDiagnostics) { return; } // if localSymbol is defined on node then node itself is exported - check is required let symbol = node.localSymbol; if (!symbol) { // local symbol is undefined => this declaration is non-exported. // however symbol might contain other declarations that are exported symbol = getSymbolOfNode(node)!; if (!symbol.exportSymbol) { // this is a pure local symbol (all declarations are non-exported) - no need to check anything return; } } // run the check only for the first declaration in the list if (getDeclarationOfKind(symbol, node.kind) !== node) { return; } let exportedDeclarationSpaces = DeclarationSpaces.None; let nonExportedDeclarationSpaces = DeclarationSpaces.None; let defaultExportedDeclarationSpaces = DeclarationSpaces.None; for (const d of symbol.declarations) { const declarationSpaces = getDeclarationSpaces(d); const effectiveDeclarationFlags = getEffectiveDeclarationFlags(d, ModifierFlags.Export | ModifierFlags.Default); if (effectiveDeclarationFlags & ModifierFlags.Export) { if (effectiveDeclarationFlags & ModifierFlags.Default) { defaultExportedDeclarationSpaces |= declarationSpaces; } else { exportedDeclarationSpaces |= declarationSpaces; } } else { nonExportedDeclarationSpaces |= declarationSpaces; } } // Spaces for anything not declared a 'default export'. const nonDefaultExportedDeclarationSpaces = exportedDeclarationSpaces | nonExportedDeclarationSpaces; const commonDeclarationSpacesForExportsAndLocals = exportedDeclarationSpaces & nonExportedDeclarationSpaces; const commonDeclarationSpacesForDefaultAndNonDefault = defaultExportedDeclarationSpaces & nonDefaultExportedDeclarationSpaces; if (commonDeclarationSpacesForExportsAndLocals || commonDeclarationSpacesForDefaultAndNonDefault) { // declaration spaces for exported and non-exported declarations intersect for (const d of symbol.declarations) { const declarationSpaces = getDeclarationSpaces(d); const name = getNameOfDeclaration(d); // Only error on the declarations that contributed to the intersecting spaces. if (declarationSpaces & commonDeclarationSpacesForDefaultAndNonDefault) { error(name, Diagnostics.Merged_declaration_0_cannot_include_a_default_export_declaration_Consider_adding_a_separate_export_default_0_declaration_instead, declarationNameToString(name)); } else if (declarationSpaces & commonDeclarationSpacesForExportsAndLocals) { error(name, Diagnostics.Individual_declarations_in_merged_declaration_0_must_be_all_exported_or_all_local, declarationNameToString(name)); } } } function getDeclarationSpaces(decl: Declaration): DeclarationSpaces { let d = decl as Node; switch (d.kind) { case SyntaxKind.InterfaceDeclaration: case SyntaxKind.TypeAliasDeclaration: // A jsdoc typedef and callback are, by definition, type aliases. // falls through case SyntaxKind.JSDocTypedefTag: case SyntaxKind.JSDocCallbackTag: case SyntaxKind.JSDocEnumTag: return DeclarationSpaces.ExportType; case SyntaxKind.ModuleDeclaration: return isAmbientModule(d as ModuleDeclaration) || getModuleInstanceState(d as ModuleDeclaration) !== ModuleInstanceState.NonInstantiated ? DeclarationSpaces.ExportNamespace | DeclarationSpaces.ExportValue : DeclarationSpaces.ExportNamespace; case SyntaxKind.ClassDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.EnumMember: return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue; case SyntaxKind.SourceFile: return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue | DeclarationSpaces.ExportNamespace; case SyntaxKind.ExportAssignment: // Export assigned entity name expressions act as aliases and should fall through, otherwise they export values if (!isEntityNameExpression((d as ExportAssignment).expression)) { return DeclarationSpaces.ExportValue; } d = (d as ExportAssignment).expression; // The below options all declare an Alias, which is allowed to merge with other values within the importing module. // falls through case SyntaxKind.ImportEqualsDeclaration: case SyntaxKind.NamespaceImport: case SyntaxKind.ImportClause: let result = DeclarationSpaces.None; const target = resolveAlias(getSymbolOfNode(d)!); forEach(target.declarations, d => { result |= getDeclarationSpaces(d); }); return result; case SyntaxKind.VariableDeclaration: case SyntaxKind.BindingElement: case SyntaxKind.FunctionDeclaration: case SyntaxKind.ImportSpecifier: // https://github.com/Microsoft/TypeScript/pull/7591 case SyntaxKind.Identifier: // https://github.com/microsoft/TypeScript/issues/36098 // Identifiers are used as declarations of assignment declarations whose parents may be // SyntaxKind.CallExpression - `Object.defineProperty(thing, "aField", {value: 42});` // SyntaxKind.ElementAccessExpression - `thing["aField"] = 42;` or `thing["aField"];` (with a doc comment on it) // or SyntaxKind.PropertyAccessExpression - `thing.aField = 42;` // all of which are pretty much always values, or at least imply a value meaning. // It may be apprpriate to treat these as aliases in the future. return DeclarationSpaces.ExportValue; default: return Debug.failBadSyntaxKind(d); } } } function getAwaitedTypeOfPromise(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { const promisedType = getPromisedTypeOfPromise(type, errorNode); return promisedType && getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0); } /** * Gets the "promised type" of a promise. * @param type The type of the promise. * @remarks The "promised type" of a type is the type of the "value" parameter of the "onfulfilled" callback. */ function getPromisedTypeOfPromise(type: Type, errorNode?: Node): Type | undefined { // // { // type // then( // thenFunction // onfulfilled: ( // onfulfilledParameterType // value: T // valueParameterType // ) => any // ): any; // } // if (isTypeAny(type)) { return undefined; } const typeAsPromise = type; if (typeAsPromise.promisedTypeOfPromise) { return typeAsPromise.promisedTypeOfPromise; } if (isReferenceToType(type, getGlobalPromiseType(/*reportErrors*/ false))) { return typeAsPromise.promisedTypeOfPromise = getTypeArguments(type)[0]; } const thenFunction = getTypeOfPropertyOfType(type, "then" as __String)!; // TODO: GH#18217 if (isTypeAny(thenFunction)) { return undefined; } const thenSignatures = thenFunction ? getSignaturesOfType(thenFunction, SignatureKind.Call) : emptyArray; if (thenSignatures.length === 0) { if (errorNode) { error(errorNode, Diagnostics.A_promise_must_have_a_then_method); } return undefined; } const onfulfilledParameterType = getTypeWithFacts(getUnionType(map(thenSignatures, getTypeOfFirstParameterOfSignature)), TypeFacts.NEUndefinedOrNull); if (isTypeAny(onfulfilledParameterType)) { return undefined; } const onfulfilledParameterSignatures = getSignaturesOfType(onfulfilledParameterType, SignatureKind.Call); if (onfulfilledParameterSignatures.length === 0) { if (errorNode) { error(errorNode, Diagnostics.The_first_parameter_of_the_then_method_of_a_promise_must_be_a_callback); } return undefined; } return typeAsPromise.promisedTypeOfPromise = getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature), UnionReduction.Subtype); } /** * Gets the "awaited type" of a type. * @param type The type to await. * @remarks The "awaited type" of an expression is its "promised type" if the expression is a * Promise-like type; otherwise, it is the type of the expression. This is used to reflect * The runtime behavior of the `await` keyword. */ function checkAwaitedType(type: Type, errorNode: Node, diagnosticMessage: DiagnosticMessage, arg0?: string | number): Type { const awaitedType = getAwaitedType(type, errorNode, diagnosticMessage, arg0); return awaitedType || errorType; } /** * Determines whether a type has a callable `then` member. */ function isThenableType(type: Type): boolean { const thenFunction = getTypeOfPropertyOfType(type, "then" as __String); return !!thenFunction && getSignaturesOfType(getTypeWithFacts(thenFunction, TypeFacts.NEUndefinedOrNull), SignatureKind.Call).length > 0; } /** * Gets the "awaited type" of a type. * * The "awaited type" of an expression is its "promised type" if the expression is a * Promise-like type; otherwise, it is the type of the expression. If the "promised * type" is itself a Promise-like, the "promised type" is recursively unwrapped until a * non-promise type is found. * * This is used to reflect the runtime behavior of the `await` keyword. */ function getAwaitedType(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { if (isTypeAny(type)) { return type; } const typeAsAwaitable = type; if (typeAsAwaitable.awaitedTypeOfType) { return typeAsAwaitable.awaitedTypeOfType; } // For a union, get a union of the awaited types of each constituent. // return typeAsAwaitable.awaitedTypeOfType = mapType(type, errorNode ? constituentType => getAwaitedTypeWorker(constituentType, errorNode, diagnosticMessage, arg0) : getAwaitedTypeWorker); } function getAwaitedTypeWorker(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, arg0?: string | number): Type | undefined { const typeAsAwaitable = type; if (typeAsAwaitable.awaitedTypeOfType) { return typeAsAwaitable.awaitedTypeOfType; } const promisedType = getPromisedTypeOfPromise(type); if (promisedType) { if (type.id === promisedType.id || awaitedTypeStack.lastIndexOf(promisedType.id) >= 0) { // Verify that we don't have a bad actor in the form of a promise whose // promised type is the same as the promise type, or a mutually recursive // promise. If so, we return undefined as we cannot guess the shape. If this // were the actual case in the JavaScript, this Promise would never resolve. // // An example of a bad actor with a singly-recursive promise type might // be: // // interface BadPromise { // then( // onfulfilled: (value: BadPromise) => any, // onrejected: (error: any) => any): BadPromise; // } // // The above interface will pass the PromiseLike check, and return a // promised type of `BadPromise`. Since this is a self reference, we // don't want to keep recursing ad infinitum. // // An example of a bad actor in the form of a mutually-recursive // promise type might be: // // interface BadPromiseA { // then( // onfulfilled: (value: BadPromiseB) => any, // onrejected: (error: any) => any): BadPromiseB; // } // // interface BadPromiseB { // then( // onfulfilled: (value: BadPromiseA) => any, // onrejected: (error: any) => any): BadPromiseA; // } // if (errorNode) { error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method); } return undefined; } // Keep track of the type we're about to unwrap to avoid bad recursive promise types. // See the comments above for more information. awaitedTypeStack.push(type.id); const awaitedType = getAwaitedType(promisedType, errorNode, diagnosticMessage, arg0); awaitedTypeStack.pop(); if (!awaitedType) { return undefined; } return typeAsAwaitable.awaitedTypeOfType = awaitedType; } // The type was not a promise, so it could not be unwrapped any further. // As long as the type does not have a callable "then" property, it is // safe to return the type; otherwise, an error is reported and we return // undefined. // // An example of a non-promise "thenable" might be: // // await { then(): void {} } // // The "thenable" does not match the minimal definition for a promise. When // a Promise/A+-compatible or ES6 promise tries to adopt this value, the promise // will never settle. We treat this as an error to help flag an early indicator // of a runtime problem. If the user wants to return this value from an async // function, they would need to wrap it in some other value. If they want it to // be treated as a promise, they can cast to . if (isThenableType(type)) { if (errorNode) { if (!diagnosticMessage) return Debug.fail(); error(errorNode, diagnosticMessage, arg0); } return undefined; } return typeAsAwaitable.awaitedTypeOfType = type; } /** * Checks the return type of an async function to ensure it is a compatible * Promise implementation. * * This checks that an async function has a valid Promise-compatible return type. * An async function has a valid Promise-compatible return type if the resolved value * of the return type has a construct signature that takes in an `initializer` function * that in turn supplies a `resolve` function as one of its arguments and results in an * object with a callable `then` signature. * * @param node The signature to check */ function checkAsyncFunctionReturnType(node: FunctionLikeDeclaration | MethodSignature, returnTypeNode: TypeNode) { // As part of our emit for an async function, we will need to emit the entity name of // the return type annotation as an expression. To meet the necessary runtime semantics // for __awaiter, we must also check that the type of the declaration (e.g. the static // side or "constructor" of the promise type) is compatible `PromiseConstructorLike`. // // An example might be (from lib.es6.d.ts): // // interface Promise { ... } // interface PromiseConstructor { // new (...): Promise; // } // declare var Promise: PromiseConstructor; // // When an async function declares a return type annotation of `Promise`, we // need to get the type of the `Promise` variable declaration above, which would // be `PromiseConstructor`. // // The same case applies to a class: // // declare class Promise { // constructor(...); // then(...): Promise; // } // const returnType = getTypeFromTypeNode(returnTypeNode); if (languageVersion >= ScriptTarget.ES2015) { if (returnType === errorType) { return; } const globalPromiseType = getGlobalPromiseType(/*reportErrors*/ true); if (globalPromiseType !== emptyGenericType && !isReferenceToType(returnType, globalPromiseType)) { // The promise type was not a valid type reference to the global promise type, so we // report an error and return the unknown type. error(returnTypeNode, Diagnostics.The_return_type_of_an_async_function_or_method_must_be_the_global_Promise_T_type_Did_you_mean_to_write_Promise_0, typeToString(getAwaitedType(returnType) || voidType)); return; } } else { // Always mark the type node as referenced if it points to a value markTypeNodeAsReferenced(returnTypeNode); if (returnType === errorType) { return; } const promiseConstructorName = getEntityNameFromTypeNode(returnTypeNode); if (promiseConstructorName === undefined) { error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, typeToString(returnType)); return; } const promiseConstructorSymbol = resolveEntityName(promiseConstructorName, SymbolFlags.Value, /*ignoreErrors*/ true); const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : errorType; if (promiseConstructorType === errorType) { if (promiseConstructorName.kind === SyntaxKind.Identifier && promiseConstructorName.escapedText === "Promise" && getTargetType(returnType) === getGlobalPromiseType(/*reportErrors*/ false)) { error(returnTypeNode, Diagnostics.An_async_function_or_method_in_ES5_SlashES3_requires_the_Promise_constructor_Make_sure_you_have_a_declaration_for_the_Promise_constructor_or_include_ES2015_in_your_lib_option); } else { error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); } return; } const globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType(/*reportErrors*/ true); if (globalPromiseConstructorLikeType === emptyObjectType) { // If we couldn't resolve the global PromiseConstructorLike type we cannot verify // compatibility with __awaiter. error(returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value, entityNameToString(promiseConstructorName)); return; } if (!checkTypeAssignableTo(promiseConstructorType, globalPromiseConstructorLikeType, returnTypeNode, Diagnostics.Type_0_is_not_a_valid_async_function_return_type_in_ES5_SlashES3_because_it_does_not_refer_to_a_Promise_compatible_constructor_value)) { return; } // Verify there is no local declaration that could collide with the promise constructor. const rootName = promiseConstructorName && getFirstIdentifier(promiseConstructorName); const collidingSymbol = getSymbol(node.locals!, rootName.escapedText, SymbolFlags.Value); if (collidingSymbol) { error(collidingSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions, idText(rootName), entityNameToString(promiseConstructorName)); return; } } checkAwaitedType(returnType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); } /** Check a decorator */ function checkDecorator(node: Decorator): void { const signature = getResolvedSignature(node); checkDeprecatedSignature(signature, node); const returnType = getReturnTypeOfSignature(signature); if (returnType.flags & TypeFlags.Any) { return; } let expectedReturnType: Type; const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); let errorInfo: DiagnosticMessageChain | undefined; switch (node.parent.kind) { case SyntaxKind.ClassDeclaration: const classSymbol = getSymbolOfNode(node.parent); const classConstructorType = getTypeOfSymbol(classSymbol); expectedReturnType = getUnionType([classConstructorType, voidType]); break; case SyntaxKind.Parameter: expectedReturnType = voidType; errorInfo = chainDiagnosticMessages( /*details*/ undefined, Diagnostics.The_return_type_of_a_parameter_decorator_function_must_be_either_void_or_any); break; case SyntaxKind.PropertyDeclaration: expectedReturnType = voidType; errorInfo = chainDiagnosticMessages( /*details*/ undefined, Diagnostics.The_return_type_of_a_property_decorator_function_must_be_either_void_or_any); break; case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: const methodType = getTypeOfNode(node.parent); const descriptorType = createTypedPropertyDescriptorType(methodType); expectedReturnType = getUnionType([descriptorType, voidType]); break; default: return Debug.fail(); } checkTypeAssignableTo( returnType, expectedReturnType, node, headMessage, () => errorInfo); } /** * 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) { markEntityNameOrEntityExpressionAsReference(node && getEntityNameFromTypeNode(node)); } function markEntityNameOrEntityExpressionAsReference(typeName: EntityNameOrEntityNameExpression | undefined) { 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, /*nameArg*/ undefined, /*isRefernce*/ true); if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias && symbolIsValue(rootSymbol) && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol)) && !getTypeOnlyAliasDeclaration(rootSymbol)) { markAliasSymbolAsReferenced(rootSymbol); } } /** * 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); } } function getEntityNameForDecoratorMetadata(node: TypeNode | undefined): EntityName | undefined { if (node) { switch (node.kind) { case SyntaxKind.IntersectionType: case SyntaxKind.UnionType: return getEntityNameForDecoratorMetadataFromTypeList((node).types); case SyntaxKind.ConditionalType: return getEntityNameForDecoratorMetadataFromTypeList([(node).trueType, (node).falseType]); case SyntaxKind.ParenthesizedType: case SyntaxKind.NamedTupleMember: return getEntityNameForDecoratorMetadata((node).type); case SyntaxKind.TypeReference: return (node).typeName; } } } function getEntityNameForDecoratorMetadataFromTypeList(types: readonly TypeNode[]): EntityName | undefined { let commonEntityName: EntityName | undefined; for (let typeNode of types) { while (typeNode.kind === SyntaxKind.ParenthesizedType || typeNode.kind === SyntaxKind.NamedTupleMember) { typeNode = (typeNode as ParenthesizedTypeNode | NamedTupleMember).type; // Skip parens if need be } if (typeNode.kind === SyntaxKind.NeverKeyword) { continue; // Always elide `never` from the union/intersection if possible } if (!strictNullChecks && (typeNode.kind === SyntaxKind.LiteralType && (typeNode as LiteralTypeNode).literal.kind === SyntaxKind.NullKeyword || typeNode.kind === SyntaxKind.UndefinedKeyword)) { continue; // Elide null and undefined from unions for metadata, just like what we did prior to the implementation of strict null checks } const individualEntityName = getEntityNameForDecoratorMetadata(typeNode); if (!individualEntityName) { // Individual is something like string number // So it would be serialized to either that type or object // Safe to return here return undefined; } if (commonEntityName) { // Note this is in sync with the transformation that happens for type node. // Keep this in sync with serializeUnionOrIntersectionType // Verify if they refer to same entity and is identifier // return undefined if they dont match because we would emit object if (!isIdentifier(commonEntityName) || !isIdentifier(individualEntityName) || commonEntityName.escapedText !== individualEntityName.escapedText) { return undefined; } } else { commonEntityName = individualEntityName; } } return commonEntityName; } function getParameterTypeNodeForDecoratorCheck(node: ParameterDeclaration): TypeNode | undefined { const typeNode = getEffectiveTypeAnnotationNode(node); return isRestParameter(node) ? getRestParameterElementType(typeNode) : typeNode; } /** Check the decorators of a node */ function checkDecorators(node: Node): void { if (!node.decorators) { return; } // skip this check for nodes that cannot have decorators. These should have already had an error reported by // checkGrammarDecorators. if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) { return; } if (!compilerOptions.experimentalDecorators) { error(node, Diagnostics.Experimental_support_for_decorators_is_a_feature_that_is_subject_to_change_in_a_future_release_Set_the_experimentalDecorators_option_in_your_tsconfig_or_jsconfig_to_remove_this_warning); } const firstDecorator = node.decorators[0]; checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Decorate); if (node.kind === SyntaxKind.Parameter) { checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Param); } if (compilerOptions.emitDecoratorMetadata) { checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata); // we only need to perform these checks if we are emitting serialized type metadata for the target of a decorator. switch (node.kind) { case SyntaxKind.ClassDeclaration: const constructor = getFirstConstructorWithBody(node); if (constructor) { for (const parameter of constructor.parameters) { markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); } } break; case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; const otherAccessor = getDeclarationOfKind(getSymbolOfNode(node as AccessorDeclaration), otherKind); markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node as AccessorDeclaration) || 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 as ParameterDeclaration).parent; for (const parameter of containingSignature.parameters) { markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); } break; } } forEach(node.decorators, checkDecorator); } function checkFunctionDeclaration(node: FunctionDeclaration): void { if (produceDiagnostics) { checkFunctionOrMethodDeclaration(node); checkGrammarForGenerator(node); checkCollisionWithRequireExportsInGeneratedCode(node, node.name!); checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name!); } } function checkJSDocTypeAliasTag(node: JSDocTypedefTag | JSDocCallbackTag) { if (!node.typeExpression) { // If the node had `@property` tags, `typeExpression` would have been set to the first property tag. error(node.name, Diagnostics.JSDoc_typedef_tag_should_either_have_a_type_annotation_or_be_followed_by_property_or_member_tags); } if (node.name) { checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); } checkSourceElement(node.typeExpression); } function checkJSDocTemplateTag(node: JSDocTemplateTag): void { checkSourceElement(node.constraint); for (const tp of node.typeParameters) { checkSourceElement(tp); } } function checkJSDocTypeTag(node: JSDocTypeTag) { checkSourceElement(node.typeExpression); } function checkJSDocParameterTag(node: JSDocParameterTag) { checkSourceElement(node.typeExpression); if (!getParameterSymbolFromJSDoc(node)) { const decl = getHostSignatureFromJSDoc(node); // don't issue an error for invalid hosts -- just functions -- // and give a better error message when the host function mentions `arguments` // but the tag doesn't have an array type if (decl) { const i = getJSDocTags(decl).filter(isJSDocParameterTag).indexOf(node); if (i > -1 && i < decl.parameters.length && isBindingPattern(decl.parameters[i].name)) { return; } if (!containsArgumentsReference(decl)) { if (isQualifiedName(node.name)) { error(node.name, Diagnostics.Qualified_name_0_is_not_allowed_without_a_leading_param_object_1, entityNameToString(node.name), entityNameToString(node.name.left)); } else { error(node.name, Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name, idText(node.name)); } } else if (findLast(getJSDocTags(decl), isJSDocParameterTag) === node && node.typeExpression && node.typeExpression.type && !isArrayType(getTypeFromTypeNode(node.typeExpression.type))) { error(node.name, Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name_It_would_match_arguments_if_it_had_an_array_type, idText(node.name.kind === SyntaxKind.QualifiedName ? node.name.right : node.name)); } } } } function checkJSDocPropertyTag(node: JSDocPropertyTag) { checkSourceElement(node.typeExpression); } function checkJSDocFunctionType(node: JSDocFunctionType): void { if (produceDiagnostics && !node.type && !isJSDocConstructSignature(node)) { reportImplicitAny(node, anyType); } checkSignatureDeclaration(node); } function checkJSDocImplementsTag(node: JSDocImplementsTag): void { const classLike = getEffectiveJSDocHost(node); if (!classLike || !isClassDeclaration(classLike) && !isClassExpression(classLike)) { error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName)); } } function checkJSDocAugmentsTag(node: JSDocAugmentsTag): void { const classLike = getEffectiveJSDocHost(node); if (!classLike || !isClassDeclaration(classLike) && !isClassExpression(classLike)) { error(classLike, Diagnostics.JSDoc_0_is_not_attached_to_a_class, idText(node.tagName)); return; } const augmentsTags = getJSDocTags(classLike).filter(isJSDocAugmentsTag); Debug.assert(augmentsTags.length > 0); if (augmentsTags.length > 1) { error(augmentsTags[1], Diagnostics.Class_declarations_cannot_have_more_than_one_augments_or_extends_tag); } const name = getIdentifierFromEntityNameExpression(node.class.expression); const extend = getClassExtendsHeritageElement(classLike); if (extend) { const className = getIdentifierFromEntityNameExpression(extend.expression); if (className && name.escapedText !== className.escapedText) { error(name, Diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, idText(node.tagName), idText(name), idText(className)); } } } function getIdentifierFromEntityNameExpression(node: Identifier | PropertyAccessExpression): Identifier | PrivateIdentifier; function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateIdentifier | undefined; function getIdentifierFromEntityNameExpression(node: Expression): Identifier | PrivateIdentifier | undefined { switch (node.kind) { case SyntaxKind.Identifier: return node as Identifier; case SyntaxKind.PropertyAccessExpression: return (node as PropertyAccessExpression).name; default: return undefined; } } function checkFunctionOrMethodDeclaration(node: FunctionDeclaration | MethodDeclaration | MethodSignature): void { checkDecorators(node); checkSignatureDeclaration(node); const functionFlags = getFunctionFlags(node); // Do not use hasDynamicName here, because that returns false for well known symbols. // We want to perform checkComputedPropertyName for all computed properties, including // well known symbols. if (node.name && node.name.kind === SyntaxKind.ComputedPropertyName) { // This check will account for methods in class/interface declarations, // as well as accessors in classes/object literals checkComputedPropertyName(node.name); } if (!hasNonBindableDynamicName(node)) { // first we want to check the local symbol that contain this declaration // - if node.localSymbol !== undefined - this is current declaration is exported and localSymbol points to the local symbol // - if node.localSymbol === undefined - this node is non-exported so we can just pick the result of getSymbolOfNode const symbol = getSymbolOfNode(node); const localSymbol = node.localSymbol || symbol; // Since the javascript won't do semantic analysis like typescript, // if the javascript file comes before the typescript file and both contain same name functions, // checkFunctionOrConstructorSymbol wouldn't be called if we didnt ignore javascript function. const firstDeclaration = find(localSymbol.declarations, // Get first non javascript function declaration declaration => declaration.kind === node.kind && !(declaration.flags & NodeFlags.JavaScriptFile)); // Only type check the symbol once if (node === firstDeclaration) { checkFunctionOrConstructorSymbol(localSymbol); } if (symbol.parent) { // run check on export symbol to check that modifiers agree across all exported declarations checkFunctionOrConstructorSymbol(symbol); } } const body = node.kind === SyntaxKind.MethodSignature ? undefined : node.body; checkSourceElement(body); checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, getReturnTypeFromAnnotation(node)); if (produceDiagnostics && !getEffectiveReturnTypeNode(node)) { // Report an implicit any error if there is no body, no explicit return type, and node is not a private method // in an ambient context if (nodeIsMissing(body) && !isPrivateWithinAmbient(node)) { reportImplicitAny(node, anyType); } if (functionFlags & FunctionFlags.Generator && nodeIsPresent(body)) { // A generator with a body and no type annotation can still cause errors. It can error if the // yielded values have no common supertype, or it can give an implicit any error if it has no // yielded values. The only way to trigger these errors is to try checking its return type. getReturnTypeOfSignature(getSignatureFromDeclaration(node)); } } // A js function declaration can have a @type tag instead of a return type node, but that type must have a call signature if (isInJSFile(node)) { const typeTag = getJSDocTypeTag(node); if (typeTag && typeTag.typeExpression && !getContextualCallSignature(getTypeFromTypeNode(typeTag.typeExpression), node)) { error(typeTag, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature); } } } function registerForUnusedIdentifiersCheck(node: PotentiallyUnusedIdentifier): void { // May be in a call such as getTypeOfNode that happened to call this. But potentiallyUnusedIdentifiers is only defined in the scope of `checkSourceFile`. if (produceDiagnostics) { const sourceFile = getSourceFileOfNode(node); let potentiallyUnusedIdentifiers = allPotentiallyUnusedIdentifiers.get(sourceFile.path); if (!potentiallyUnusedIdentifiers) { potentiallyUnusedIdentifiers = []; allPotentiallyUnusedIdentifiers.set(sourceFile.path, potentiallyUnusedIdentifiers); } // TODO: GH#22580 // Debug.assert(addToSeen(seenPotentiallyUnusedIdentifiers, getNodeId(node)), "Adding potentially-unused identifier twice"); potentiallyUnusedIdentifiers.push(node); } } type PotentiallyUnusedIdentifier = | SourceFile | ModuleDeclaration | ClassLikeDeclaration | InterfaceDeclaration | Block | CaseBlock | ForStatement | ForInStatement | ForOfStatement | Exclude | TypeAliasDeclaration | InferTypeNode; function checkUnusedIdentifiers(potentiallyUnusedIdentifiers: readonly PotentiallyUnusedIdentifier[], addDiagnostic: AddUnusedDiagnostic) { for (const node of potentiallyUnusedIdentifiers) { switch (node.kind) { case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: checkUnusedClassMembers(node, addDiagnostic); checkUnusedTypeParameters(node, addDiagnostic); break; case SyntaxKind.SourceFile: case SyntaxKind.ModuleDeclaration: case SyntaxKind.Block: case SyntaxKind.CaseBlock: case SyntaxKind.ForStatement: case SyntaxKind.ForInStatement: case SyntaxKind.ForOfStatement: checkUnusedLocalsAndParameters(node, addDiagnostic); break; case SyntaxKind.Constructor: case SyntaxKind.FunctionExpression: case SyntaxKind.FunctionDeclaration: case SyntaxKind.ArrowFunction: case SyntaxKind.MethodDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: if (node.body) { // Don't report unused parameters in overloads checkUnusedLocalsAndParameters(node, addDiagnostic); } checkUnusedTypeParameters(node, addDiagnostic); break; case SyntaxKind.MethodSignature: case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.InterfaceDeclaration: checkUnusedTypeParameters(node, addDiagnostic); break; case SyntaxKind.InferType: checkUnusedInferTypeParameter(node, addDiagnostic); break; default: Debug.assertNever(node, "Node should not have been registered for unused identifiers check"); } } } function errorUnusedLocal(declaration: Declaration, name: string, addDiagnostic: AddUnusedDiagnostic) { const node = getNameOfDeclaration(declaration) || declaration; const message = isTypeDeclaration(declaration) ? Diagnostics._0_is_declared_but_never_used : Diagnostics._0_is_declared_but_its_value_is_never_read; addDiagnostic(declaration, UnusedKind.Local, createDiagnosticForNode(node, message, name)); } function isIdentifierThatStartsWithUnderscore(node: Node) { return isIdentifier(node) && idText(node).charCodeAt(0) === CharacterCodes._; } function checkUnusedClassMembers(node: ClassDeclaration | ClassExpression, addDiagnostic: AddUnusedDiagnostic): void { for (const member of node.members) { switch (member.kind) { case SyntaxKind.MethodDeclaration: case SyntaxKind.PropertyDeclaration: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: if (member.kind === SyntaxKind.SetAccessor && member.symbol.flags & SymbolFlags.GetAccessor) { // Already would have reported an error on the getter. break; } const symbol = getSymbolOfNode(member); if (!symbol.isReferenced && (hasEffectiveModifier(member, ModifierFlags.Private) || isNamedDeclaration(member) && isPrivateIdentifier(member.name)) && !(member.flags & NodeFlags.Ambient)) { addDiagnostic(member, UnusedKind.Local, createDiagnosticForNode(member.name!, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolToString(symbol))); } break; case SyntaxKind.Constructor: for (const parameter of (member).parameters) { if (!parameter.symbol.isReferenced && hasSyntacticModifier(parameter, ModifierFlags.Private)) { addDiagnostic(parameter, UnusedKind.Local, createDiagnosticForNode(parameter.name, Diagnostics.Property_0_is_declared_but_its_value_is_never_read, symbolName(parameter.symbol))); } } break; case SyntaxKind.IndexSignature: case SyntaxKind.SemicolonClassElement: // Can't be private break; default: Debug.fail(); } } } function checkUnusedInferTypeParameter(node: InferTypeNode, addDiagnostic: AddUnusedDiagnostic): void { const { typeParameter } = node; if (isTypeParameterUnused(typeParameter)) { addDiagnostic(node, UnusedKind.Parameter, createDiagnosticForNode(node, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(typeParameter.name))); } } function checkUnusedTypeParameters(node: ClassLikeDeclaration | SignatureDeclaration | InterfaceDeclaration | TypeAliasDeclaration, addDiagnostic: AddUnusedDiagnostic): void { // Only report errors on the last declaration for the type parameter container; // this ensures that all uses have been accounted for. if (last(getSymbolOfNode(node).declarations) !== node) return; const typeParameters = getEffectiveTypeParameterDeclarations(node); const seenParentsWithEveryUnused = new Set(); for (const typeParameter of typeParameters) { if (!isTypeParameterUnused(typeParameter)) continue; const name = idText(typeParameter.name); const { parent } = typeParameter; if (parent.kind !== SyntaxKind.InferType && parent.typeParameters!.every(isTypeParameterUnused)) { if (tryAddToSet(seenParentsWithEveryUnused, parent)) { const range = isJSDocTemplateTag(parent) // Whole @template tag ? rangeOfNode(parent) // Include the `<>` in the error message : rangeOfTypeParameters(parent.typeParameters!); const only = parent.typeParameters!.length === 1; const message = only ? Diagnostics._0_is_declared_but_its_value_is_never_read : Diagnostics.All_type_parameters_are_unused; const arg0 = only ? name : undefined; addDiagnostic(typeParameter, UnusedKind.Parameter, createFileDiagnostic(getSourceFileOfNode(parent), range.pos, range.end - range.pos, message, arg0)); } } else { addDiagnostic(typeParameter, UnusedKind.Parameter, createDiagnosticForNode(typeParameter, Diagnostics._0_is_declared_but_its_value_is_never_read, name)); } } } function isTypeParameterUnused(typeParameter: TypeParameterDeclaration): boolean { return !(getMergedSymbol(typeParameter.symbol).isReferenced! & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderscore(typeParameter.name); } function addToGroup(map: ESMap, key: K, value: V, getKey: (key: K) => number | string): void { const keyString = String(getKey(key)); const group = map.get(keyString); if (group) { group[1].push(value); } else { map.set(keyString, [key, [value]]); } } function tryGetRootParameterDeclaration(node: Node): ParameterDeclaration | undefined { return tryCast(getRootDeclaration(node), isParameter); } function isValidUnusedLocalDeclaration(declaration: Declaration): boolean { if (isBindingElement(declaration) && isIdentifierThatStartsWithUnderscore(declaration.name)) { return !!findAncestor(declaration.parent, ancestor => isArrayBindingPattern(ancestor) || isVariableDeclaration(ancestor) || isVariableDeclarationList(ancestor) ? false : isForOfStatement(ancestor) ? true : "quit" ); } return isAmbientModule(declaration) || (isVariableDeclaration(declaration) && isForInOrOfStatement(declaration.parent.parent) || isImportedDeclaration(declaration)) && isIdentifierThatStartsWithUnderscore(declaration.name!); } function checkUnusedLocalsAndParameters(nodeWithLocals: Node, addDiagnostic: AddUnusedDiagnostic): void { // Ideally we could use the ImportClause directly as a key, but must wait until we have full ES6 maps. So must store key along with value. const unusedImports = new Map(); const unusedDestructures = new Map(); const unusedVariables = new Map(); nodeWithLocals.locals!.forEach(local => { // If it's purely a type parameter, ignore, will be checked in `checkUnusedTypeParameters`. // If it's a type parameter merged with a parameter, check if the parameter-side is used. if (local.flags & SymbolFlags.TypeParameter ? !(local.flags & SymbolFlags.Variable && !(local.isReferenced! & SymbolFlags.Variable)) : local.isReferenced || local.exportSymbol) { return; } for (const declaration of local.declarations) { if (isValidUnusedLocalDeclaration(declaration)) { continue; } if (isImportedDeclaration(declaration)) { addToGroup(unusedImports, importClauseFromImported(declaration), declaration, getNodeId); } else if (isBindingElement(declaration) && isObjectBindingPattern(declaration.parent)) { // In `{ a, ...b }, `a` is considered used since it removes a property from `b`. `b` may still be unused though. const lastElement = last(declaration.parent.elements); if (declaration === lastElement || !last(declaration.parent.elements).dotDotDotToken) { addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); } } else if (isVariableDeclaration(declaration)) { addToGroup(unusedVariables, declaration.parent, declaration, getNodeId); } else { const parameter = local.valueDeclaration && tryGetRootParameterDeclaration(local.valueDeclaration); const name = local.valueDeclaration && getNameOfDeclaration(local.valueDeclaration); if (parameter && name) { if (!isParameterPropertyDeclaration(parameter, parameter.parent) && !parameterIsThisKeyword(parameter) && !isIdentifierThatStartsWithUnderscore(name)) { addDiagnostic(parameter, UnusedKind.Parameter, createDiagnosticForNode(name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(local))); } } else { errorUnusedLocal(declaration, symbolName(local), addDiagnostic); } } } }); unusedImports.forEach(([importClause, unuseds]) => { const importDecl = importClause.parent; const nDeclarations = (importClause.name ? 1 : 0) + (importClause.namedBindings ? (importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? 1 : importClause.namedBindings.elements.length) : 0); if (nDeclarations === unuseds.length) { addDiagnostic(importDecl, UnusedKind.Local, unuseds.length === 1 ? createDiagnosticForNode(importDecl, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(first(unuseds).name!)) : createDiagnosticForNode(importDecl, Diagnostics.All_imports_in_import_declaration_are_unused)); } else { for (const unused of unuseds) errorUnusedLocal(unused, idText(unused.name!), addDiagnostic); } }); unusedDestructures.forEach(([bindingPattern, bindingElements]) => { const kind = tryGetRootParameterDeclaration(bindingPattern.parent) ? UnusedKind.Parameter : UnusedKind.Local; if (bindingPattern.elements.length === bindingElements.length) { if (bindingElements.length === 1 && bindingPattern.parent.kind === SyntaxKind.VariableDeclaration && bindingPattern.parent.parent.kind === SyntaxKind.VariableDeclarationList) { addToGroup(unusedVariables, bindingPattern.parent.parent, bindingPattern.parent, getNodeId); } else { addDiagnostic(bindingPattern, kind, bindingElements.length === 1 ? createDiagnosticForNode(bindingPattern, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(bindingElements).name)) : createDiagnosticForNode(bindingPattern, Diagnostics.All_destructured_elements_are_unused)); } } else { for (const e of bindingElements) { addDiagnostic(e, kind, createDiagnosticForNode(e, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(e.name))); } } }); unusedVariables.forEach(([declarationList, declarations]) => { if (declarationList.declarations.length === declarations.length) { addDiagnostic(declarationList, UnusedKind.Local, declarations.length === 1 ? createDiagnosticForNode(first(declarations).name, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(declarations).name)) : createDiagnosticForNode(declarationList.parent.kind === SyntaxKind.VariableStatement ? declarationList.parent : declarationList, Diagnostics.All_variables_are_unused)); } else { for (const decl of declarations) { addDiagnostic(decl, UnusedKind.Local, createDiagnosticForNode(decl, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(decl.name))); } } }); } function bindingNameText(name: BindingName): string { switch (name.kind) { case SyntaxKind.Identifier: return idText(name); case SyntaxKind.ArrayBindingPattern: case SyntaxKind.ObjectBindingPattern: return bindingNameText(cast(first(name.elements), isBindingElement).name); default: return Debug.assertNever(name); } } type ImportedDeclaration = ImportClause | ImportSpecifier | NamespaceImport; function isImportedDeclaration(node: Node): node is ImportedDeclaration { return node.kind === SyntaxKind.ImportClause || node.kind === SyntaxKind.ImportSpecifier || node.kind === SyntaxKind.NamespaceImport; } function importClauseFromImported(decl: ImportedDeclaration): ImportClause { return decl.kind === SyntaxKind.ImportClause ? decl : decl.kind === SyntaxKind.NamespaceImport ? decl.parent : decl.parent.parent; } function checkBlock(node: Block) { // Grammar checking for SyntaxKind.Block if (node.kind === SyntaxKind.Block) { checkGrammarStatementInAmbientContext(node); } if (isFunctionOrModuleBlock(node)) { const saveFlowAnalysisDisabled = flowAnalysisDisabled; forEach(node.statements, checkSourceElement); flowAnalysisDisabled = saveFlowAnalysisDisabled; } else { forEach(node.statements, checkSourceElement); } if (node.locals) { registerForUnusedIdentifiersCheck(node); } } function checkCollisionWithArgumentsInGeneratedCode(node: SignatureDeclaration) { // no rest parameters \ declaration context \ overload - no codegen impact if (languageVersion >= ScriptTarget.ES2015 || !hasRestParameter(node) || node.flags & NodeFlags.Ambient || nodeIsMissing((node).body)) { return; } forEach(node.parameters, p => { if (p.name && !isBindingPattern(p.name) && p.name.escapedText === argumentsSymbol.escapedName) { errorSkippedOn("noEmit", p, Diagnostics.Duplicate_identifier_arguments_Compiler_uses_arguments_to_initialize_rest_parameters); } }); } function needCollisionCheckForIdentifier(node: Node, identifier: Identifier | undefined, name: string): boolean { if (!(identifier && identifier.escapedText === name)) { return false; } if (node.kind === SyntaxKind.PropertyDeclaration || node.kind === SyntaxKind.PropertySignature || node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.MethodSignature || node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor) { // it is ok to have member named '_super' or '_this' - member access is always qualified return false; } if (node.flags & NodeFlags.Ambient) { // ambient context - no codegen impact return false; } const root = getRootDeclaration(node); if (root.kind === SyntaxKind.Parameter && nodeIsMissing((root.parent).body)) { // just an overload - no codegen impact return false; } return true; } // this function will run after checking the source file so 'CaptureThis' is correct for all nodes function checkIfThisIsCapturedInEnclosingScope(node: Node): void { findAncestor(node, current => { if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureThis) { const isDeclaration = node.kind !== SyntaxKind.Identifier; if (isDeclaration) { error(getNameOfDeclaration(node), Diagnostics.Duplicate_identifier_this_Compiler_uses_variable_declaration_this_to_capture_this_reference); } else { error(node, Diagnostics.Expression_resolves_to_variable_declaration_this_that_compiler_uses_to_capture_this_reference); } return true; } return false; }); } function checkIfNewTargetIsCapturedInEnclosingScope(node: Node): void { findAncestor(node, current => { if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureNewTarget) { const isDeclaration = node.kind !== SyntaxKind.Identifier; if (isDeclaration) { error(getNameOfDeclaration(node), Diagnostics.Duplicate_identifier_newTarget_Compiler_uses_variable_declaration_newTarget_to_capture_new_target_meta_property_reference); } else { error(node, Diagnostics.Expression_resolves_to_variable_declaration_newTarget_that_compiler_uses_to_capture_new_target_meta_property_reference); } return true; } return false; }); } function checkWeakMapCollision(node: Node) { const enclosingBlockScope = getEnclosingBlockScopeContainer(node); if (getNodeCheckFlags(enclosingBlockScope) & NodeCheckFlags.ContainsClassWithPrivateIdentifiers) { errorSkippedOn("noEmit", node, Diagnostics.Compiler_reserves_name_0_when_emitting_private_identifier_downlevel, "WeakMap"); } } function checkCollisionWithRequireExportsInGeneratedCode(node: Node, name: Identifier) { // No need to check for require or exports for ES6 modules and later if (moduleKind >= ModuleKind.ES2015) { return; } if (!needCollisionCheckForIdentifier(node, name, "require") && !needCollisionCheckForIdentifier(node, name, "exports")) { return; } // Uninstantiated modules shouldnt do this check if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { return; } // In case of variable declaration, node.parent is variable statement so look at the variable statement's parent const parent = getDeclarationContainer(node); if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(parent)) { // If the declaration happens to be in external module, report error that require and exports are reserved keywords errorSkippedOn("noEmit", name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module, declarationNameToString(name), declarationNameToString(name)); } } function checkCollisionWithGlobalPromiseInGeneratedCode(node: Node, name: Identifier): void { if (languageVersion >= ScriptTarget.ES2017 || !needCollisionCheckForIdentifier(node, name, "Promise")) { return; } // Uninstantiated modules shouldnt do this check if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { return; } // In case of variable declaration, node.parent is variable statement so look at the variable statement's parent const parent = getDeclarationContainer(node); if (parent.kind === SyntaxKind.SourceFile && isExternalOrCommonJsModule(parent) && parent.flags & NodeFlags.HasAsyncFunctions) { // If the declaration happens to be in external module, report error that Promise is a reserved identifier. errorSkippedOn("noEmit", name, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_in_top_level_scope_of_a_module_containing_async_functions, declarationNameToString(name), declarationNameToString(name)); } } function checkVarDeclaredNamesNotShadowed(node: VariableDeclaration | BindingElement) { // - ScriptBody : StatementList // It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList // also occurs in the VarDeclaredNames of StatementList. // - Block : { StatementList } // It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList // also occurs in the VarDeclaredNames of StatementList. // Variable declarations are hoisted to the top of their function scope. They can shadow // block scoped declarations, which bind tighter. this will not be flagged as duplicate definition // by the binder as the declaration scope is different. // A non-initialized declaration is a no-op as the block declaration will resolve before the var // declaration. the problem is if the declaration has an initializer. this will act as a write to the // block declared value. this is fine for let, but not const. // Only consider declarations with initializers, uninitialized const declarations will not // step on a let/const variable. // Do not consider const and const declarations, as duplicate block-scoped declarations // are handled by the binder. // We are only looking for const declarations that step on let\const declarations from a // different scope. e.g.: // { // const x = 0; // localDeclarationSymbol obtained after name resolution will correspond to this declaration // const x = 0; // symbol for this declaration will be 'symbol' // } // skip block-scoped variables and parameters if ((getCombinedNodeFlags(node) & NodeFlags.BlockScoped) !== 0 || isParameterDeclaration(node)) { return; } // skip variable declarations that don't have initializers // NOTE: in ES6 spec initializer is required in variable declarations where name is binding pattern // so we'll always treat binding elements as initialized if (node.kind === SyntaxKind.VariableDeclaration && !node.initializer) { return; } const symbol = getSymbolOfNode(node); if (symbol.flags & SymbolFlags.FunctionScopedVariable) { if (!isIdentifier(node.name)) return Debug.fail(); const localDeclarationSymbol = resolveName(node, node.name.escapedText, SymbolFlags.Variable, /*nodeNotFoundErrorMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); if (localDeclarationSymbol && localDeclarationSymbol !== symbol && localDeclarationSymbol.flags & SymbolFlags.BlockScopedVariable) { if (getDeclarationNodeFlagsFromSymbol(localDeclarationSymbol) & NodeFlags.BlockScoped) { const varDeclList = getAncestor(localDeclarationSymbol.valueDeclaration, SyntaxKind.VariableDeclarationList)!; const container = varDeclList.parent.kind === SyntaxKind.VariableStatement && varDeclList.parent.parent ? varDeclList.parent.parent : undefined; // names of block-scoped and function scoped variables can collide only // if block scoped variable is defined in the function\module\source file scope (because of variable hoisting) const namesShareScope = container && (container.kind === SyntaxKind.Block && isFunctionLike(container.parent) || container.kind === SyntaxKind.ModuleBlock || container.kind === SyntaxKind.ModuleDeclaration || container.kind === SyntaxKind.SourceFile); // here we know that function scoped variable is shadowed by block scoped one // if they are defined in the same scope - binder has already reported redeclaration error // otherwise if variable has an initializer - show error that initialization will fail // since LHS will be block scoped name instead of function scoped if (!namesShareScope) { const name = symbolToString(localDeclarationSymbol); error(node, Diagnostics.Cannot_initialize_outer_scoped_variable_0_in_the_same_scope_as_block_scoped_declaration_1, name, name); } } } } } function convertAutoToAny(type: Type) { return type === autoType ? anyType : type === autoArrayType ? anyArrayType : type; } // Check variable, parameter, or property declaration function checkVariableLikeDeclaration(node: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement) { checkDecorators(node); if (!isBindingElement(node)) { checkSourceElement(node.type); } // JSDoc `function(string, string): string` syntax results in parameters with no name if (!node.name) { return; } // For a computed property, just check the initializer and exit // Do not use hasDynamicName here, because that returns false for well known symbols. // We want to perform checkComputedPropertyName for all computed properties, including // well known symbols. if (node.name.kind === SyntaxKind.ComputedPropertyName) { checkComputedPropertyName(node.name); if (node.initializer) { checkExpressionCached(node.initializer); } } if (node.kind === SyntaxKind.BindingElement) { if (node.parent.kind === SyntaxKind.ObjectBindingPattern && languageVersion < ScriptTarget.ESNext) { checkExternalEmitHelpers(node, ExternalEmitHelpers.Rest); } // check computed properties inside property names of binding elements if (node.propertyName && node.propertyName.kind === SyntaxKind.ComputedPropertyName) { checkComputedPropertyName(node.propertyName); } // check private/protected variable access const parent = node.parent.parent; const parentType = getTypeForBindingElementParent(parent); const name = node.propertyName || node.name; if (parentType && !isBindingPattern(name)) { const exprType = getLiteralTypeFromPropertyName(name); if (isTypeUsableAsPropertyName(exprType)) { const nameText = getPropertyNameFromType(exprType); const property = getPropertyOfType(parentType, nameText); if (property) { markPropertyAsReferenced(property, /*nodeForCheckWriteOnly*/ undefined, /*isThisAccess*/ false); // A destructuring is never a write-only reference. checkPropertyAccessibility(parent, !!parent.initializer && parent.initializer.kind === SyntaxKind.SuperKeyword, parentType, property); } } } } // For a binding pattern, check contained binding elements if (isBindingPattern(node.name)) { if (node.name.kind === SyntaxKind.ArrayBindingPattern && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { checkExternalEmitHelpers(node, ExternalEmitHelpers.Read); } forEach(node.name.elements, checkSourceElement); } // For a parameter declaration with an initializer, error and exit if the containing function doesn't have a body if (node.initializer && isParameterDeclaration(node) && nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) { error(node, Diagnostics.A_parameter_initializer_is_only_allowed_in_a_function_or_constructor_implementation); return; } // For a binding pattern, validate the initializer and exit if (isBindingPattern(node.name)) { const needCheckInitializer = node.initializer && node.parent.parent.kind !== SyntaxKind.ForInStatement; const needCheckWidenedType = node.name.elements.length === 0; if (needCheckInitializer || needCheckWidenedType) { // Don't validate for-in initializer as it is already an error const widenedType = getWidenedTypeForVariableLikeDeclaration(node); if (needCheckInitializer) { const initializerType = checkExpressionCached(node.initializer!); if (strictNullChecks && needCheckWidenedType) { checkNonNullNonVoidType(initializerType, node); } else { checkTypeAssignableToAndOptionallyElaborate(initializerType, getWidenedTypeForVariableLikeDeclaration(node), node, node.initializer); } } // check the binding pattern with empty elements if (needCheckWidenedType) { if (isArrayBindingPattern(node.name)) { checkIteratedTypeOrElementType(IterationUse.Destructuring, widenedType, undefinedType, node); } else if (strictNullChecks) { checkNonNullNonVoidType(widenedType, node); } } } return; } // For a commonjs `const x = require`, validate the alias and exit const symbol = getSymbolOfNode(node); if (symbol.flags & SymbolFlags.Alias && isRequireVariableDeclaration(node, /*requireStringLiteralLikeArgument*/ true)) { checkAliasSymbol(node); return; } const type = convertAutoToAny(getTypeOfSymbol(symbol)); if (node === symbol.valueDeclaration) { // Node is the primary declaration of the symbol, just validate the initializer // Don't validate for-in initializer as it is already an error const initializer = getEffectiveInitializer(node); if (initializer) { const isJSObjectLiteralInitializer = isInJSFile(node) && isObjectLiteralExpression(initializer) && (initializer.properties.length === 0 || isPrototypeAccess(node.name)) && !!symbol.exports?.size; if (!isJSObjectLiteralInitializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) { checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(initializer), type, node, initializer, /*headMessage*/ undefined); } } if (symbol.declarations.length > 1) { if (some(symbol.declarations, d => d !== node && isVariableLike(d) && !areDeclarationFlagsIdentical(d, node))) { error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); } } } else { // Node is a secondary declaration, check that type is identical to primary declaration and check that // initializer is consistent with type associated with the node const declarationType = convertAutoToAny(getWidenedTypeForVariableLikeDeclaration(node)); if (type !== errorType && declarationType !== errorType && !isTypeIdenticalTo(type, declarationType) && !(symbol.flags & SymbolFlags.Assignment)) { errorNextVariableOrPropertyDeclarationMustHaveSameType(symbol.valueDeclaration, type, node, declarationType); } if (node.initializer) { checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(node.initializer), declarationType, node, node.initializer, /*headMessage*/ undefined); } if (!areDeclarationFlagsIdentical(node, symbol.valueDeclaration)) { error(node.name, Diagnostics.All_declarations_of_0_must_have_identical_modifiers, declarationNameToString(node.name)); } } if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature) { // We know we don't have a binding pattern or computed name here checkExportsOnMergedDeclarations(node); if (node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement) { checkVarDeclaredNamesNotShadowed(node); } checkCollisionWithRequireExportsInGeneratedCode(node, node.name); checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name); if (languageVersion < ScriptTarget.ESNext && needCollisionCheckForIdentifier(node, node.name, "WeakMap")) { potentialWeakMapCollisions.push(node); } } } function errorNextVariableOrPropertyDeclarationMustHaveSameType(firstDeclaration: Declaration | undefined, firstType: Type, nextDeclaration: Declaration, nextType: Type): void { const nextDeclarationName = getNameOfDeclaration(nextDeclaration); const message = nextDeclaration.kind === SyntaxKind.PropertyDeclaration || nextDeclaration.kind === SyntaxKind.PropertySignature ? Diagnostics.Subsequent_property_declarations_must_have_the_same_type_Property_0_must_be_of_type_1_but_here_has_type_2 : Diagnostics.Subsequent_variable_declarations_must_have_the_same_type_Variable_0_must_be_of_type_1_but_here_has_type_2; const declName = declarationNameToString(nextDeclarationName); const err = error( nextDeclarationName, message, declName, typeToString(firstType), typeToString(nextType) ); if (firstDeclaration) { addRelatedInfo(err, createDiagnosticForNode(firstDeclaration, Diagnostics._0_was_also_declared_here, declName) ); } } function areDeclarationFlagsIdentical(left: Declaration, right: Declaration) { if ((left.kind === SyntaxKind.Parameter && right.kind === SyntaxKind.VariableDeclaration) || (left.kind === SyntaxKind.VariableDeclaration && right.kind === SyntaxKind.Parameter)) { // Differences in optionality between parameters and variables are allowed. return true; } if (hasQuestionToken(left) !== hasQuestionToken(right)) { return false; } const interestingFlags = ModifierFlags.Private | ModifierFlags.Protected | ModifierFlags.Async | ModifierFlags.Abstract | ModifierFlags.Readonly | ModifierFlags.Static; return getSelectedEffectiveModifierFlags(left, interestingFlags) === getSelectedEffectiveModifierFlags(right, interestingFlags); } function checkVariableDeclaration(node: VariableDeclaration) { tracing.push(tracing.Phase.Check, "checkVariableDeclaration", { kind: node.kind, pos: node.pos, end: node.end }); checkGrammarVariableDeclaration(node); checkVariableLikeDeclaration(node); tracing.pop(); } function checkBindingElement(node: BindingElement) { checkGrammarBindingElement(node); return checkVariableLikeDeclaration(node); } function checkVariableStatement(node: VariableStatement) { // Grammar checking if (!checkGrammarDecoratorsAndModifiers(node) && !checkGrammarVariableDeclarationList(node.declarationList)) checkGrammarForDisallowedLetOrConstStatement(node); forEach(node.declarationList.declarations, checkSourceElement); } function checkExpressionStatement(node: ExpressionStatement) { // Grammar checking checkGrammarStatementInAmbientContext(node); checkExpression(node.expression); } function checkIfStatement(node: IfStatement) { // Grammar checking checkGrammarStatementInAmbientContext(node); const type = checkTruthinessExpression(node.expression); checkTestingKnownTruthyCallableType(node.expression, node.thenStatement, type); checkSourceElement(node.thenStatement); if (node.thenStatement.kind === SyntaxKind.EmptyStatement) { error(node.thenStatement, Diagnostics.The_body_of_an_if_statement_cannot_be_the_empty_statement); } checkSourceElement(node.elseStatement); } function checkTestingKnownTruthyCallableType(condExpr: Expression, body: Statement | Expression, type: Type) { if (!strictNullChecks) { return; } const testedNode = isIdentifier(condExpr) ? condExpr : isPropertyAccessExpression(condExpr) ? condExpr.name : undefined; if (!testedNode) { return; } const possiblyFalsy = getFalsyFlags(type); if (possiblyFalsy) { return; } // While it technically should be invalid for any known-truthy value // to be tested, we de-scope to functions unrefenced in the block as a // heuristic to identify the most common bugs. There are too many // false positives for values sourced from type definitions without // strictNullChecks otherwise. const callSignatures = getSignaturesOfType(type, SignatureKind.Call); if (callSignatures.length === 0) { return; } const testedFunctionSymbol = getSymbolAtLocation(testedNode); if (!testedFunctionSymbol) { return; } const functionIsUsedInBody = forEachChild(body, function check(childNode): boolean | undefined { if (isIdentifier(childNode)) { const childSymbol = getSymbolAtLocation(childNode); if (childSymbol && childSymbol === testedFunctionSymbol) { // If the test was a simple identifier, the above check is sufficient if (isIdentifier(condExpr)) { return true; } // Otherwise we need to ensure the symbol is called on the same target let testedExpression = testedNode.parent; let childExpression = childNode.parent; while (testedExpression && childExpression) { if (isIdentifier(testedExpression) && isIdentifier(childExpression) || testedExpression.kind === SyntaxKind.ThisKeyword && childExpression.kind === SyntaxKind.ThisKeyword ) { return getSymbolAtLocation(testedExpression) === getSymbolAtLocation(childExpression); } if (isPropertyAccessExpression(testedExpression) && isPropertyAccessExpression(childExpression)) { if (getSymbolAtLocation(testedExpression.name) !== getSymbolAtLocation(childExpression.name)) { return false; } childExpression = childExpression.expression; testedExpression = testedExpression.expression; } else { return false; } } } } return forEachChild(childNode, check); }); if (!functionIsUsedInBody) { error(condExpr, Diagnostics.This_condition_will_always_return_true_since_the_function_is_always_defined_Did_you_mean_to_call_it_instead); } } function checkDoStatement(node: DoStatement) { // Grammar checking checkGrammarStatementInAmbientContext(node); checkSourceElement(node.statement); checkTruthinessExpression(node.expression); } function checkWhileStatement(node: WhileStatement) { // Grammar checking checkGrammarStatementInAmbientContext(node); checkTruthinessExpression(node.expression); checkSourceElement(node.statement); } function checkTruthinessOfType(type: Type, node: Node) { if (type.flags & TypeFlags.Void) { error(node, Diagnostics.An_expression_of_type_void_cannot_be_tested_for_truthiness); } return type; } function checkTruthinessExpression(node: Expression, checkMode?: CheckMode) { return checkTruthinessOfType(checkExpression(node, checkMode), node); } function checkForStatement(node: ForStatement) { // Grammar checking if (!checkGrammarStatementInAmbientContext(node)) { if (node.initializer && node.initializer.kind === SyntaxKind.VariableDeclarationList) { checkGrammarVariableDeclarationList(node.initializer); } } if (node.initializer) { if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { forEach((node.initializer).declarations, checkVariableDeclaration); } else { checkExpression(node.initializer); } } if (node.condition) checkTruthinessExpression(node.condition); if (node.incrementor) checkExpression(node.incrementor); checkSourceElement(node.statement); if (node.locals) { registerForUnusedIdentifiersCheck(node); } } function checkForOfStatement(node: ForOfStatement): void { checkGrammarForInOrForOfStatement(node); if (node.awaitModifier) { const functionFlags = getFunctionFlags(getContainingFunction(node)); if ((functionFlags & (FunctionFlags.Invalid | FunctionFlags.Async)) === FunctionFlags.Async && languageVersion < ScriptTarget.ESNext) { // for..await..of in an async function or async generator function prior to ESNext requires the __asyncValues helper checkExternalEmitHelpers(node, ExternalEmitHelpers.ForAwaitOfIncludes); } } else if (compilerOptions.downlevelIteration && languageVersion < ScriptTarget.ES2015) { // for..of prior to ES2015 requires the __values helper when downlevelIteration is enabled checkExternalEmitHelpers(node, ExternalEmitHelpers.ForOfIncludes); } // Check the LHS and RHS // If the LHS is a declaration, just check it as a variable declaration, which will in turn check the RHS // via checkRightHandSideOfForOf. // If the LHS is an expression, check the LHS, as a destructuring assignment or as a reference. // Then check that the RHS is assignable to it. if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { checkForInOrForOfVariableDeclaration(node); } else { const varExpr = node.initializer; const iteratedType = checkRightHandSideOfForOf(node); // There may be a destructuring assignment on the left side if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) { // iteratedType may be undefined. In this case, we still want to check the structure of // varExpr, in particular making sure it's a valid LeftHandSideExpression. But we'd like // to short circuit the type relation checking as much as possible, so we pass the unknownType. checkDestructuringAssignment(varExpr, iteratedType || errorType); } else { const leftType = checkExpression(varExpr); checkReferenceExpression( varExpr, Diagnostics.The_left_hand_side_of_a_for_of_statement_must_be_a_variable_or_a_property_access, Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_an_optional_property_access); // iteratedType will be undefined if the rightType 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. if (iteratedType) { checkTypeAssignableToAndOptionallyElaborate(iteratedType, leftType, varExpr, node.expression); } } } checkSourceElement(node.statement); if (node.locals) { registerForUnusedIdentifiersCheck(node); } } function checkForInStatement(node: ForInStatement) { // Grammar checking checkGrammarForInOrForOfStatement(node); const rightType = getNonNullableTypeIfNeeded(checkExpression(node.expression)); // TypeScript 1.0 spec (April 2014): 5.4 // In a 'for-in' statement of the form // for (let VarDecl in Expr) Statement // VarDecl must be a variable declaration without a type annotation that declares a variable of type Any, // and Expr must be an expression of type Any, an object type, or a type parameter type. if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { const variable = (node.initializer).declarations[0]; if (variable && isBindingPattern(variable.name)) { error(variable.name, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern); } checkForInOrForOfVariableDeclaration(node); } else { // In a 'for-in' statement of the form // for (Var in Expr) Statement // Var must be an expression classified as a reference of type Any or the String primitive type, // and Expr must be an expression of type Any, an object type, or a type parameter type. const varExpr = node.initializer; const leftType = checkExpression(varExpr); if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) { error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern); } else if (!isTypeAssignableTo(getIndexTypeOrString(rightType), leftType)) { error(varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_of_type_string_or_any); } else { // run check only former check succeeded to avoid cascading errors checkReferenceExpression( varExpr, Diagnostics.The_left_hand_side_of_a_for_in_statement_must_be_a_variable_or_a_property_access, Diagnostics.The_left_hand_side_of_a_for_in_statement_may_not_be_an_optional_property_access); } } // unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved // in this case error about missing name is already reported - do not report extra one if (rightType === neverType || !isTypeAssignableToKind(rightType, TypeFlags.NonPrimitive | TypeFlags.InstantiableNonPrimitive)) { error(node.expression, Diagnostics.The_right_hand_side_of_a_for_in_statement_must_be_of_type_any_an_object_type_or_a_type_parameter_but_here_has_type_0, typeToString(rightType)); } checkSourceElement(node.statement); if (node.locals) { registerForUnusedIdentifiersCheck(node); } } function checkForInOrForOfVariableDeclaration(iterationStatement: ForInOrOfStatement): void { const variableDeclarationList = iterationStatement.initializer; // checkGrammarForInOrForOfStatement will check that there is exactly one declaration. if (variableDeclarationList.declarations.length >= 1) { const decl = variableDeclarationList.declarations[0]; checkVariableDeclaration(decl); } } function checkRightHandSideOfForOf(statement: ForOfStatement): Type { const use = statement.awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf; return checkIteratedTypeOrElementType(use, checkNonNullExpression(statement.expression), undefinedType, statement.expression); } function checkIteratedTypeOrElementType(use: IterationUse, inputType: Type, sentType: Type, errorNode: Node | undefined): Type { if (isTypeAny(inputType)) { return inputType; } return getIteratedTypeOrElementType(use, inputType, sentType, errorNode, /*checkAssignability*/ true) || anyType; } /** * When consuming an iterable type in a for..of, spread, or iterator destructuring assignment * we want to get the iterated type of an iterable for ES2015 or later, or the iterated type * of a iterable (if defined globally) or element type of an array like for ES2015 or earlier. */ function getIteratedTypeOrElementType(use: IterationUse, inputType: Type, sentType: Type, errorNode: Node | undefined, checkAssignability: boolean): Type | undefined { const allowAsyncIterables = (use & IterationUse.AllowsAsyncIterablesFlag) !== 0; if (inputType === neverType) { reportTypeNotIterableError(errorNode!, inputType, allowAsyncIterables); // TODO: GH#18217 return undefined; } const uplevelIteration = languageVersion >= ScriptTarget.ES2015; const downlevelIteration = !uplevelIteration && compilerOptions.downlevelIteration; const possibleOutOfBounds = compilerOptions.noUncheckedIndexedAccess && !!(use & IterationUse.PossiblyOutOfBounds); // Get the iterated type of an `Iterable` or `IterableIterator` only in ES2015 // or higher, when inside of an async generator or for-await-if, or when // downlevelIteration is requested. if (uplevelIteration || downlevelIteration || allowAsyncIterables) { // We only report errors for an invalid iterable type in ES2015 or higher. const iterationTypes = getIterationTypesOfIterable(inputType, use, uplevelIteration ? errorNode : undefined); if (checkAssignability) { if (iterationTypes) { const diagnostic = use & IterationUse.ForOfFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_for_of_will_always_send_0 : use & IterationUse.SpreadFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_array_spread_will_always_send_0 : use & IterationUse.DestructuringFlag ? Diagnostics.Cannot_iterate_value_because_the_next_method_of_its_iterator_expects_type_1_but_array_destructuring_will_always_send_0 : use & IterationUse.YieldStarFlag ? Diagnostics.Cannot_delegate_iteration_to_value_because_the_next_method_of_its_iterator_expects_type_1_but_the_containing_generator_will_always_send_0 : undefined; if (diagnostic) { checkTypeAssignableTo(sentType, iterationTypes.nextType, errorNode, diagnostic); } } } if (iterationTypes || uplevelIteration) { return possibleOutOfBounds ? includeUndefinedInIndexSignature(iterationTypes && iterationTypes.yieldType) : (iterationTypes && iterationTypes.yieldType); } } let arrayType = inputType; let reportedError = false; let hasStringConstituent = false; // If strings are permitted, remove any string-like constituents from the array type. // This allows us to find other non-string element types from an array unioned with // a string. if (use & IterationUse.AllowsStringInputFlag) { if (arrayType.flags & TypeFlags.Union) { // After we remove all types that are StringLike, we will know if there was a string constituent // based on whether the result of filter is a new array. const arrayTypes = (inputType).types; const filteredTypes = filter(arrayTypes, t => !(t.flags & TypeFlags.StringLike)); if (filteredTypes !== arrayTypes) { arrayType = getUnionType(filteredTypes, UnionReduction.Subtype); } } else if (arrayType.flags & TypeFlags.StringLike) { arrayType = neverType; } hasStringConstituent = arrayType !== inputType; if (hasStringConstituent) { if (languageVersion < ScriptTarget.ES5) { if (errorNode) { error(errorNode, Diagnostics.Using_a_string_in_a_for_of_statement_is_only_supported_in_ECMAScript_5_and_higher); reportedError = true; } } // Now that we've removed all the StringLike types, if no constituents remain, then the entire // arrayOrStringType was a string. if (arrayType.flags & TypeFlags.Never) { return possibleOutOfBounds ? includeUndefinedInIndexSignature(stringType) : stringType; } } } if (!isArrayLikeType(arrayType)) { if (errorNode && !reportedError) { // Which error we report depends on whether we allow strings or if there was a // string constituent. For example, if the input type is number | string, we // want to say that number is not an array type. But if the input was just // number and string input is allowed, we want to say that number is not an // array type or a string type. const yieldType = getIterationTypeOfIterable(use, IterationTypeKind.Yield, inputType, /*errorNode*/ undefined); const [defaultDiagnostic, maybeMissingAwait]: [DiagnosticMessage, boolean] = !(use & IterationUse.AllowsStringInputFlag) || hasStringConstituent ? downlevelIteration ? [Diagnostics.Type_0_is_not_an_array_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true] : yieldType ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_Use_compiler_option_downlevelIteration_to_allow_iterating_of_iterators, false] : [Diagnostics.Type_0_is_not_an_array_type, true] : downlevelIteration ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true] : yieldType ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_Use_compiler_option_downlevelIteration_to_allow_iterating_of_iterators, false] : [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type, true]; errorAndMaybeSuggestAwait( errorNode, maybeMissingAwait && !!getAwaitedTypeOfPromise(arrayType), defaultDiagnostic, typeToString(arrayType)); } return hasStringConstituent ? possibleOutOfBounds ? includeUndefinedInIndexSignature(stringType) : stringType : undefined; } const arrayElementType = getIndexTypeOfType(arrayType, IndexKind.Number); if (hasStringConstituent && arrayElementType) { // This is just an optimization for the case where arrayOrStringType is string | string[] if (arrayElementType.flags & TypeFlags.StringLike && !compilerOptions.noUncheckedIndexedAccess) { return stringType; } return getUnionType(possibleOutOfBounds ? [arrayElementType, stringType, undefinedType] : [arrayElementType, stringType], UnionReduction.Subtype); } return (use & IterationUse.PossiblyOutOfBounds) ? includeUndefinedInIndexSignature(arrayElementType) : arrayElementType; } /** * Gets the requested "iteration type" from an `Iterable`-like or `AsyncIterable`-like type. */ function getIterationTypeOfIterable(use: IterationUse, typeKind: IterationTypeKind, inputType: Type, errorNode: Node | undefined): Type | undefined { if (isTypeAny(inputType)) { return undefined; } const iterationTypes = getIterationTypesOfIterable(inputType, use, errorNode); return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(typeKind)]; } function createIterationTypes(yieldType: Type = neverType, returnType: Type = neverType, nextType: Type = unknownType): IterationTypes { // `yieldType` and `returnType` are defaulted to `neverType` they each will be combined // via `getUnionType` when merging iteration types. `nextType` is defined as `unknownType` // as it is combined via `getIntersectionType` when merging iteration types. // Use the cache only for intrinsic types to keep it small as they are likely to be // more frequently created (i.e. `Iterator`). Iteration types // are also cached on the type they are requested for, so we shouldn't need to maintain // the cache for less-frequently used types. if (yieldType.flags & TypeFlags.Intrinsic && returnType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Unknown | TypeFlags.Void | TypeFlags.Undefined) && nextType.flags & (TypeFlags.Any | TypeFlags.Never | TypeFlags.Unknown | TypeFlags.Void | TypeFlags.Undefined)) { const id = getTypeListId([yieldType, returnType, nextType]); let iterationTypes = iterationTypesCache.get(id); if (!iterationTypes) { iterationTypes = { yieldType, returnType, nextType }; iterationTypesCache.set(id, iterationTypes); } return iterationTypes; } return { yieldType, returnType, nextType }; } /** * Combines multiple `IterationTypes` records. * * If `array` is empty or all elements are missing or are references to `noIterationTypes`, * then `noIterationTypes` is returned. Otherwise, an `IterationTypes` record is returned * for the combined iteration types. */ function combineIterationTypes(array: (IterationTypes | undefined)[]) { let yieldTypes: Type[] | undefined; let returnTypes: Type[] | undefined; let nextTypes: Type[] | undefined; for (const iterationTypes of array) { if (iterationTypes === undefined || iterationTypes === noIterationTypes) { continue; } if (iterationTypes === anyIterationTypes) { return anyIterationTypes; } yieldTypes = append(yieldTypes, iterationTypes.yieldType); returnTypes = append(returnTypes, iterationTypes.returnType); nextTypes = append(nextTypes, iterationTypes.nextType); } if (yieldTypes || returnTypes || nextTypes) { return createIterationTypes( yieldTypes && getUnionType(yieldTypes), returnTypes && getUnionType(returnTypes), nextTypes && getIntersectionType(nextTypes)); } return noIterationTypes; } function getCachedIterationTypes(type: Type, cacheKey: MatchingKeys) { return (type as IterableOrIteratorType)[cacheKey]; } function setCachedIterationTypes(type: Type, cacheKey: MatchingKeys, cachedTypes: IterationTypes) { return (type as IterableOrIteratorType)[cacheKey] = cachedTypes; } /** * Gets the *yield*, *return*, and *next* types from an `Iterable`-like or `AsyncIterable`-like type. * * At every level that involves analyzing return types of signatures, we union the return types of all the signatures. * * Another thing to note is that at any step of this process, we could run into a dead end, * meaning either the property is missing, or we run into the anyType. If either of these things * happens, we return `undefined` to signal that we could not find the iteration type. If a property * is missing, and the previous step did not result in `any`, then we also give an error if the * caller requested it. Then the caller can decide what to do in the case where there is no iterated * type. * * For a **for-of** statement, `yield*` (in a normal generator), spread, array * destructuring, or normal generator we will only ever look for a `[Symbol.iterator]()` * method. * * For an async generator we will only ever look at the `[Symbol.asyncIterator]()` method. * * For a **for-await-of** statement or a `yield*` in an async generator we will look for * the `[Symbol.asyncIterator]()` method first, and then the `[Symbol.iterator]()` method. */ function getIterationTypesOfIterable(type: Type, use: IterationUse, errorNode: Node | undefined) { if (isTypeAny(type)) { return anyIterationTypes; } if (!(type.flags & TypeFlags.Union)) { const iterationTypes = getIterationTypesOfIterableWorker(type, use, errorNode); if (iterationTypes === noIterationTypes) { if (errorNode) { reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); } return undefined; } return iterationTypes; } const cacheKey = use & IterationUse.AllowsAsyncIterablesFlag ? "iterationTypesOfAsyncIterable" : "iterationTypesOfIterable"; const cachedTypes = getCachedIterationTypes(type, cacheKey); if (cachedTypes) return cachedTypes === noIterationTypes ? undefined : cachedTypes; let allIterationTypes: IterationTypes[] | undefined; for (const constituent of (type as UnionType).types) { const iterationTypes = getIterationTypesOfIterableWorker(constituent, use, errorNode); if (iterationTypes === noIterationTypes) { if (errorNode) { reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); } setCachedIterationTypes(type, cacheKey, noIterationTypes); return undefined; } else { allIterationTypes = append(allIterationTypes, iterationTypes); } } const iterationTypes = allIterationTypes ? combineIterationTypes(allIterationTypes) : noIterationTypes; setCachedIterationTypes(type, cacheKey, iterationTypes); return iterationTypes === noIterationTypes ? undefined : iterationTypes; } function getAsyncFromSyncIterationTypes(iterationTypes: IterationTypes, errorNode: Node | undefined) { if (iterationTypes === noIterationTypes) return noIterationTypes; if (iterationTypes === anyIterationTypes) return anyIterationTypes; const { yieldType, returnType, nextType } = iterationTypes; return createIterationTypes( getAwaitedType(yieldType, errorNode) || anyType, getAwaitedType(returnType, errorNode) || anyType, nextType); } /** * Gets the *yield*, *return*, and *next* types from a non-union type. * * If we are unable to find the *yield*, *return*, and *next* types, `noIterationTypes` is * returned to indicate to the caller that it should report an error. Otherwise, an * `IterationTypes` record is returned. * * NOTE: You probably don't want to call this directly and should be calling * `getIterationTypesOfIterable` instead. */ function getIterationTypesOfIterableWorker(type: Type, use: IterationUse, errorNode: Node | undefined) { if (isTypeAny(type)) { return anyIterationTypes; } if (use & IterationUse.AllowsAsyncIterablesFlag) { const iterationTypes = getIterationTypesOfIterableCached(type, asyncIterationTypesResolver) || getIterationTypesOfIterableFast(type, asyncIterationTypesResolver); if (iterationTypes) { return iterationTypes; } } if (use & IterationUse.AllowsSyncIterablesFlag) { const iterationTypes = getIterationTypesOfIterableCached(type, syncIterationTypesResolver) || getIterationTypesOfIterableFast(type, syncIterationTypesResolver); if (iterationTypes) { if (use & IterationUse.AllowsAsyncIterablesFlag) { // for a sync iterable in an async context, only use the cached types if they are valid. if (iterationTypes !== noIterationTypes) { return setCachedIterationTypes(type, "iterationTypesOfAsyncIterable", getAsyncFromSyncIterationTypes(iterationTypes, errorNode)); } } else { return iterationTypes; } } } if (use & IterationUse.AllowsAsyncIterablesFlag) { const iterationTypes = getIterationTypesOfIterableSlow(type, asyncIterationTypesResolver, errorNode); if (iterationTypes !== noIterationTypes) { return iterationTypes; } } if (use & IterationUse.AllowsSyncIterablesFlag) { const iterationTypes = getIterationTypesOfIterableSlow(type, syncIterationTypesResolver, errorNode); if (iterationTypes !== noIterationTypes) { if (use & IterationUse.AllowsAsyncIterablesFlag) { return setCachedIterationTypes(type, "iterationTypesOfAsyncIterable", iterationTypes ? getAsyncFromSyncIterationTypes(iterationTypes, errorNode) : noIterationTypes); } else { return iterationTypes; } } } return noIterationTypes; } /** * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or * `AsyncIterable`-like type from the cache. * * NOTE: You probably don't want to call this directly and should be calling * `getIterationTypesOfIterable` instead. */ function getIterationTypesOfIterableCached(type: Type, resolver: IterationTypesResolver) { return getCachedIterationTypes(type, resolver.iterableCacheKey); } function getIterationTypesOfGlobalIterableType(globalType: Type, resolver: IterationTypesResolver) { const globalIterationTypes = getIterationTypesOfIterableCached(globalType, resolver) || getIterationTypesOfIterableSlow(globalType, resolver, /*errorNode*/ undefined); return globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes; } /** * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like * type from from common heuristics. * * If we previously analyzed this type and found no iteration types, `noIterationTypes` is * returned. If we found iteration types, an `IterationTypes` record is returned. * Otherwise, we return `undefined` to indicate to the caller it should perform a more * exhaustive analysis. * * NOTE: You probably don't want to call this directly and should be calling * `getIterationTypesOfIterable` instead. */ function getIterationTypesOfIterableFast(type: Type, resolver: IterationTypesResolver) { // As an optimization, if the type is an instantiation of one of the following global types, then // just grab its related type argument: // - `Iterable` or `AsyncIterable` // - `IterableIterator` or `AsyncIterableIterator` let globalType: Type; if (isReferenceToType(type, globalType = resolver.getGlobalIterableType(/*reportErrors*/ false)) || isReferenceToType(type, globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false))) { const [yieldType] = getTypeArguments(type as GenericType); // The "return" and "next" types of `Iterable` and `IterableIterator` are defined by the // iteration types of their `[Symbol.iterator]()` method. The same is true for their async cousins. // While we define these as `any` and `undefined` in our libs by default, a custom lib *could* use // different definitions. const { returnType, nextType } = getIterationTypesOfGlobalIterableType(globalType, resolver); return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(yieldType, returnType, nextType)); } // As an optimization, if the type is an instantiation of the following global type, then // just grab its related type arguments: // - `Generator` or `AsyncGenerator` if (isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) { const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType); return setCachedIterationTypes(type, resolver.iterableCacheKey, createIterationTypes(yieldType, returnType, nextType)); } } /** * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like * type from its members. * * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` * record is returned. Otherwise, `noIterationTypes` is returned. * * NOTE: You probably don't want to call this directly and should be calling * `getIterationTypesOfIterable` instead. */ function getIterationTypesOfIterableSlow(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined) { const method = getPropertyOfType(type, getPropertyNameForKnownSymbolName(resolver.iteratorSymbolName)); const methodType = method && !(method.flags & SymbolFlags.Optional) ? getTypeOfSymbol(method) : undefined; if (isTypeAny(methodType)) { return setCachedIterationTypes(type, resolver.iterableCacheKey, anyIterationTypes); } const signatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : undefined; if (!some(signatures)) { return setCachedIterationTypes(type, resolver.iterableCacheKey, noIterationTypes); } const iteratorType = getIntersectionType(map(signatures, getReturnTypeOfSignature)); const iterationTypes = getIterationTypesOfIterator(iteratorType, resolver, errorNode) ?? noIterationTypes; return setCachedIterationTypes(type, resolver.iterableCacheKey, iterationTypes); } function reportTypeNotIterableError(errorNode: Node, type: Type, allowAsyncIterables: boolean): void { const message = allowAsyncIterables ? Diagnostics.Type_0_must_have_a_Symbol_asyncIterator_method_that_returns_an_async_iterator : Diagnostics.Type_0_must_have_a_Symbol_iterator_method_that_returns_an_iterator; errorAndMaybeSuggestAwait(errorNode, !!getAwaitedTypeOfPromise(type), message, typeToString(type)); } /** * Gets the *yield*, *return*, and *next* types from an `Iterator`-like or `AsyncIterator`-like type. * * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` * record is returned. Otherwise, `undefined` is returned. */ function getIterationTypesOfIterator(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined) { if (isTypeAny(type)) { return anyIterationTypes; } const iterationTypes = getIterationTypesOfIteratorCached(type, resolver) || getIterationTypesOfIteratorFast(type, resolver) || getIterationTypesOfIteratorSlow(type, resolver, errorNode); return iterationTypes === noIterationTypes ? undefined : iterationTypes; } /** * Gets the iteration types of an `Iterator`-like or `AsyncIterator`-like type from the * cache. * * NOTE: You probably don't want to call this directly and should be calling * `getIterationTypesOfIterator` instead. */ function getIterationTypesOfIteratorCached(type: Type, resolver: IterationTypesResolver) { return getCachedIterationTypes(type, resolver.iteratorCacheKey); } /** * Gets the iteration types of an `Iterator`-like or `AsyncIterator`-like type from the * cache or from common heuristics. * * If we previously analyzed this type and found no iteration types, `noIterationTypes` is * returned. If we found iteration types, an `IterationTypes` record is returned. * Otherwise, we return `undefined` to indicate to the caller it should perform a more * exhaustive analysis. * * NOTE: You probably don't want to call this directly and should be calling * `getIterationTypesOfIterator` instead. */ function getIterationTypesOfIteratorFast(type: Type, resolver: IterationTypesResolver) { // As an optimization, if the type is an instantiation of one of the following global types, // then just grab its related type argument: // - `IterableIterator` or `AsyncIterableIterator` // - `Iterator` or `AsyncIterator` // - `Generator` or `AsyncGenerator` const globalType = resolver.getGlobalIterableIteratorType(/*reportErrors*/ false); if (isReferenceToType(type, globalType)) { const [yieldType] = getTypeArguments(type as GenericType); // The "return" and "next" types of `IterableIterator` and `AsyncIterableIterator` are defined by the // iteration types of their `next`, `return`, and `throw` methods. While we define these as `any` // and `undefined` in our libs by default, a custom lib *could* use different definitions. const globalIterationTypes = getIterationTypesOfIteratorCached(globalType, resolver) || getIterationTypesOfIteratorSlow(globalType, resolver, /*errorNode*/ undefined); const { returnType, nextType } = globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes; return setCachedIterationTypes(type, resolver.iteratorCacheKey, createIterationTypes(yieldType, returnType, nextType)); } if (isReferenceToType(type, resolver.getGlobalIteratorType(/*reportErrors*/ false)) || isReferenceToType(type, resolver.getGlobalGeneratorType(/*reportErrors*/ false))) { const [yieldType, returnType, nextType] = getTypeArguments(type as GenericType); return setCachedIterationTypes(type, resolver.iteratorCacheKey, createIterationTypes(yieldType, returnType, nextType)); } } function isIteratorResult(type: Type, kind: IterationTypeKind.Yield | IterationTypeKind.Return) { // From https://tc39.github.io/ecma262/#sec-iteratorresult-interface: // > [done] is the result status of an iterator `next` method call. If the end of the iterator was reached `done` is `true`. // > If the end was not reached `done` is `false` and a value is available. // > If a `done` property (either own or inherited) does not exist, it is consider to have the value `false`. const doneType = getTypeOfPropertyOfType(type, "done" as __String) || falseType; return isTypeAssignableTo(kind === IterationTypeKind.Yield ? falseType : trueType, doneType); } function isYieldIteratorResult(type: Type) { return isIteratorResult(type, IterationTypeKind.Yield); } function isReturnIteratorResult(type: Type) { return isIteratorResult(type, IterationTypeKind.Return); } /** * Gets the *yield* and *return* types of an `IteratorResult`-like type. * * If we are unable to determine a *yield* or a *return* type, `noIterationTypes` is * returned to indicate to the caller that it should handle the error. Otherwise, an * `IterationTypes` record is returned. */ function getIterationTypesOfIteratorResult(type: Type) { if (isTypeAny(type)) { return anyIterationTypes; } const cachedTypes = getCachedIterationTypes(type, "iterationTypesOfIteratorResult"); if (cachedTypes) { return cachedTypes; } // As an optimization, if the type is an instantiation of one of the global `IteratorYieldResult` // or `IteratorReturnResult` types, then just grab its type argument. if (isReferenceToType(type, getGlobalIteratorYieldResultType(/*reportErrors*/ false))) { const yieldType = getTypeArguments(type as GenericType)[0]; return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(yieldType, /*returnType*/ undefined, /*nextType*/ undefined)); } if (isReferenceToType(type, getGlobalIteratorReturnResultType(/*reportErrors*/ false))) { const returnType = getTypeArguments(type as GenericType)[0]; return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(/*yieldType*/ undefined, returnType, /*nextType*/ undefined)); } // Choose any constituents that can produce the requested iteration type. const yieldIteratorResult = filterType(type, isYieldIteratorResult); const yieldType = yieldIteratorResult !== neverType ? getTypeOfPropertyOfType(yieldIteratorResult, "value" as __String) : undefined; const returnIteratorResult = filterType(type, isReturnIteratorResult); const returnType = returnIteratorResult !== neverType ? getTypeOfPropertyOfType(returnIteratorResult, "value" as __String) : undefined; if (!yieldType && !returnType) { return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", noIterationTypes); } // From https://tc39.github.io/ecma262/#sec-iteratorresult-interface // > ... If the iterator does not have a return value, `value` is `undefined`. In that case, the // > `value` property may be absent from the conforming object if it does not inherit an explicit // > `value` property. return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(yieldType, returnType || voidType, /*nextType*/ undefined)); } /** * Gets the *yield*, *return*, and *next* types of a the `next()`, `return()`, or * `throw()` method of an `Iterator`-like or `AsyncIterator`-like type. * * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` * record is returned. Otherwise, we return `undefined`. */ function getIterationTypesOfMethod(type: Type, resolver: IterationTypesResolver, methodName: "next" | "return" | "throw", errorNode: Node | undefined): IterationTypes | undefined { const method = getPropertyOfType(type, methodName as __String); // Ignore 'return' or 'throw' if they are missing. if (!method && methodName !== "next") { return undefined; } const methodType = method && !(methodName === "next" && (method.flags & SymbolFlags.Optional)) ? methodName === "next" ? getTypeOfSymbol(method) : getTypeWithFacts(getTypeOfSymbol(method), TypeFacts.NEUndefinedOrNull) : undefined; if (isTypeAny(methodType)) { // `return()` and `throw()` don't provide a *next* type. return methodName === "next" ? anyIterationTypes : anyIterationTypesExceptNext; } // Both async and non-async iterators *must* have a `next` method. const methodSignatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : emptyArray; if (methodSignatures.length === 0) { if (errorNode) { const diagnostic = methodName === "next" ? resolver.mustHaveANextMethodDiagnostic : resolver.mustBeAMethodDiagnostic; error(errorNode, diagnostic, methodName); } return methodName === "next" ? anyIterationTypes : undefined; } // If the method signature comes exclusively from the global iterator or generator type, // create iteration types from its type arguments like `getIterationTypesOfIteratorFast` // does (so as to remove `undefined` from the next and return types). We arrive here when // a contextual type for a generator was not a direct reference to one of those global types, // but looking up `methodType` referred to one of them (and nothing else). E.g., in // `interface SpecialIterator extends Iterator {}`, `SpecialIterator` is not a // reference to `Iterator`, but its `next` member derives exclusively from `Iterator`. if (methodType?.symbol && methodSignatures.length === 1) { const globalGeneratorType = resolver.getGlobalGeneratorType(/*reportErrors*/ false); const globalIteratorType = resolver.getGlobalIteratorType(/*reportErrors*/ false); const isGeneratorMethod = globalGeneratorType.symbol?.members?.get(methodName as __String) === methodType.symbol; const isIteratorMethod = !isGeneratorMethod && globalIteratorType.symbol?.members?.get(methodName as __String) === methodType.symbol; if (isGeneratorMethod || isIteratorMethod) { const globalType = isGeneratorMethod ? globalGeneratorType : globalIteratorType; const { mapper } = methodType as AnonymousType; return createIterationTypes( getMappedType(globalType.typeParameters![0], mapper!), getMappedType(globalType.typeParameters![1], mapper!), methodName === "next" ? getMappedType(globalType.typeParameters![2], mapper!) : undefined); } } // Extract the first parameter and return type of each signature. let methodParameterTypes: Type[] | undefined; let methodReturnTypes: Type[] | undefined; for (const signature of methodSignatures) { if (methodName !== "throw" && some(signature.parameters)) { methodParameterTypes = append(methodParameterTypes, getTypeAtPosition(signature, 0)); } methodReturnTypes = append(methodReturnTypes, getReturnTypeOfSignature(signature)); } // Resolve the *next* or *return* type from the first parameter of a `next()` or // `return()` method, respectively. let returnTypes: Type[] | undefined; let nextType: Type | undefined; if (methodName !== "throw") { const methodParameterType = methodParameterTypes ? getUnionType(methodParameterTypes) : unknownType; if (methodName === "next") { // The value of `next(value)` is *not* awaited by async generators nextType = methodParameterType; } else if (methodName === "return") { // The value of `return(value)` *is* awaited by async generators const resolvedMethodParameterType = resolver.resolveIterationType(methodParameterType, errorNode) || anyType; returnTypes = append(returnTypes, resolvedMethodParameterType); } } // Resolve the *yield* and *return* types from the return type of the method (i.e. `IteratorResult`) let yieldType: Type; const methodReturnType = methodReturnTypes ? getIntersectionType(methodReturnTypes) : neverType; const resolvedMethodReturnType = resolver.resolveIterationType(methodReturnType, errorNode) || anyType; const iterationTypes = getIterationTypesOfIteratorResult(resolvedMethodReturnType); if (iterationTypes === noIterationTypes) { if (errorNode) { error(errorNode, resolver.mustHaveAValueDiagnostic, methodName); } yieldType = anyType; returnTypes = append(returnTypes, anyType); } else { yieldType = iterationTypes.yieldType; returnTypes = append(returnTypes, iterationTypes.returnType); } return createIterationTypes(yieldType, getUnionType(returnTypes), nextType); } /** * Gets the *yield*, *return*, and *next* types of an `Iterator`-like or `AsyncIterator`-like * type from its members. * * If we successfully found the *yield*, *return*, and *next* types, an `IterationTypes` * record is returned. Otherwise, `noIterationTypes` is returned. * * NOTE: You probably don't want to call this directly and should be calling * `getIterationTypesOfIterator` instead. */ function getIterationTypesOfIteratorSlow(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined) { const iterationTypes = combineIterationTypes([ getIterationTypesOfMethod(type, resolver, "next", errorNode), getIterationTypesOfMethod(type, resolver, "return", errorNode), getIterationTypesOfMethod(type, resolver, "throw", errorNode), ]); return setCachedIterationTypes(type, resolver.iteratorCacheKey, iterationTypes); } /** * Gets the requested "iteration type" from a type that is either `Iterable`-like, `Iterator`-like, * `IterableIterator`-like, or `Generator`-like (for a non-async generator); or `AsyncIterable`-like, * `AsyncIterator`-like, `AsyncIterableIterator`-like, or `AsyncGenerator`-like (for an async generator). */ function getIterationTypeOfGeneratorFunctionReturnType(kind: IterationTypeKind, returnType: Type, isAsyncGenerator: boolean): Type | undefined { if (isTypeAny(returnType)) { return undefined; } const iterationTypes = getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsyncGenerator); return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(kind)]; } function getIterationTypesOfGeneratorFunctionReturnType(type: Type, isAsyncGenerator: boolean) { if (isTypeAny(type)) { return anyIterationTypes; } const use = isAsyncGenerator ? IterationUse.AsyncGeneratorReturnType : IterationUse.GeneratorReturnType; const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver; return getIterationTypesOfIterable(type, use, /*errorNode*/ undefined) || getIterationTypesOfIterator(type, resolver, /*errorNode*/ undefined); } function checkBreakOrContinueStatement(node: BreakOrContinueStatement) { // Grammar checking if (!checkGrammarStatementInAmbientContext(node)) checkGrammarBreakOrContinueStatement(node); // TODO: Check that target label is valid } function unwrapReturnType(returnType: Type, functionFlags: FunctionFlags) { const isGenerator = !!(functionFlags & FunctionFlags.Generator); const isAsync = !!(functionFlags & FunctionFlags.Async); return isGenerator ? getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, isAsync) ?? errorType : isAsync ? getAwaitedType(returnType) ?? errorType : returnType; } function isUnwrappedReturnTypeVoidOrAny(func: SignatureDeclaration, returnType: Type): boolean { const unwrappedReturnType = unwrapReturnType(returnType, getFunctionFlags(func)); return !!unwrappedReturnType && maybeTypeOfKind(unwrappedReturnType, TypeFlags.Void | TypeFlags.AnyOrUnknown); } function checkReturnStatement(node: ReturnStatement) { // Grammar checking if (checkGrammarStatementInAmbientContext(node)) { return; } const func = getContainingFunction(node); if (!func) { grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_can_only_be_used_within_a_function_body); return; } const signature = getSignatureFromDeclaration(func); const returnType = getReturnTypeOfSignature(signature); const functionFlags = getFunctionFlags(func); if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) { const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; if (func.kind === SyntaxKind.SetAccessor) { if (node.expression) { error(node, Diagnostics.Setters_cannot_return_a_value); } } else if (func.kind === SyntaxKind.Constructor) { if (node.expression && !checkTypeAssignableToAndOptionallyElaborate(exprType, returnType, node, node.expression)) { error(node, Diagnostics.Return_type_of_constructor_signature_must_be_assignable_to_the_instance_type_of_the_class); } } else if (getReturnTypeFromAnnotation(func)) { const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; const unwrappedExprType = functionFlags & FunctionFlags.Async ? checkAwaitedType(exprType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) : exprType; if (unwrappedReturnType) { // If the function has a return type, but promisedType is // undefined, an error will be reported in checkAsyncFunctionReturnType // so we don't need to report one here. checkTypeAssignableToAndOptionallyElaborate(unwrappedExprType, unwrappedReturnType, node, node.expression); } } } else if (func.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(func, returnType)) { // The function has a return type, but the return statement doesn't have an expression. error(node, Diagnostics.Not_all_code_paths_return_a_value); } } function checkWithStatement(node: WithStatement) { // Grammar checking for withStatement if (!checkGrammarStatementInAmbientContext(node)) { if (node.flags & NodeFlags.AwaitContext) { grammarErrorOnFirstToken(node, Diagnostics.with_statements_are_not_allowed_in_an_async_function_block); } } checkExpression(node.expression); const sourceFile = getSourceFileOfNode(node); if (!hasParseDiagnostics(sourceFile)) { const start = getSpanOfTokenAtPosition(sourceFile, node.pos).start; const end = node.statement.pos; grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.The_with_statement_is_not_supported_All_symbols_in_a_with_block_will_have_type_any); } } function checkSwitchStatement(node: SwitchStatement) { // Grammar checking checkGrammarStatementInAmbientContext(node); let firstDefaultClause: CaseOrDefaultClause; let hasDuplicateDefaultClause = false; const expressionType = checkExpression(node.expression); const expressionIsLiteral = isLiteralType(expressionType); forEach(node.caseBlock.clauses, clause => { // Grammar check for duplicate default clauses, skip if we already report duplicate default clause if (clause.kind === SyntaxKind.DefaultClause && !hasDuplicateDefaultClause) { if (firstDefaultClause === undefined) { firstDefaultClause = clause; } else { grammarErrorOnNode(clause, Diagnostics.A_default_clause_cannot_appear_more_than_once_in_a_switch_statement); hasDuplicateDefaultClause = true; } } if (produceDiagnostics && clause.kind === SyntaxKind.CaseClause) { // TypeScript 1.0 spec (April 2014): 5.9 // In a 'switch' statement, each 'case' expression must be of a type that is comparable // to or from the type of the 'switch' expression. let caseType = checkExpression(clause.expression); const caseIsLiteral = isLiteralType(caseType); let comparedExpressionType = expressionType; if (!caseIsLiteral || !expressionIsLiteral) { caseType = caseIsLiteral ? getBaseTypeOfLiteralType(caseType) : caseType; comparedExpressionType = getBaseTypeOfLiteralType(expressionType); } if (!isTypeEqualityComparableTo(comparedExpressionType, caseType)) { // expressionType is not comparable to caseType, try the reversed check and report errors if it fails checkTypeComparableTo(caseType, comparedExpressionType, clause.expression, /*headMessage*/ undefined); } } forEach(clause.statements, checkSourceElement); if (compilerOptions.noFallthroughCasesInSwitch && clause.fallthroughFlowNode && isReachableFlowNode(clause.fallthroughFlowNode)) { error(clause, Diagnostics.Fallthrough_case_in_switch); } }); if (node.caseBlock.locals) { registerForUnusedIdentifiersCheck(node.caseBlock); } } function checkLabeledStatement(node: LabeledStatement) { // Grammar checking if (!checkGrammarStatementInAmbientContext(node)) { findAncestor(node.parent, current => { if (isFunctionLike(current)) { return "quit"; } if (current.kind === SyntaxKind.LabeledStatement && (current).label.escapedText === node.label.escapedText) { grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getTextOfNode(node.label)); return true; } return false; }); } // ensure that label is unique checkSourceElement(node.statement); } function checkThrowStatement(node: ThrowStatement) { // Grammar checking if (!checkGrammarStatementInAmbientContext(node)) { if (isIdentifier(node.expression) && !node.expression.escapedText) { grammarErrorAfterFirstToken(node, Diagnostics.Line_break_not_permitted_here); } } if (node.expression) { checkExpression(node.expression); } } function checkTryStatement(node: TryStatement) { // Grammar checking checkGrammarStatementInAmbientContext(node); checkBlock(node.tryBlock); const catchClause = node.catchClause; if (catchClause) { // Grammar checking if (catchClause.variableDeclaration) { const declaration = catchClause.variableDeclaration; if (declaration.type) { const type = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ false); if (type && !(type.flags & TypeFlags.AnyOrUnknown)) { grammarErrorOnFirstToken(declaration.type, Diagnostics.Catch_clause_variable_type_annotation_must_be_any_or_unknown_if_specified); } } else if (declaration.initializer) { grammarErrorOnFirstToken(declaration.initializer, Diagnostics.Catch_clause_variable_cannot_have_an_initializer); } else { const blockLocals = catchClause.block.locals; if (blockLocals) { forEachKey(catchClause.locals!, caughtName => { const blockLocal = blockLocals.get(caughtName); if (blockLocal && (blockLocal.flags & SymbolFlags.BlockScopedVariable) !== 0) { grammarErrorOnNode(blockLocal.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, caughtName); } }); } } } checkBlock(catchClause.block); } if (node.finallyBlock) { checkBlock(node.finallyBlock); } } function checkIndexConstraints(type: Type) { const declaredNumberIndexer = getIndexDeclarationOfSymbol(type.symbol, IndexKind.Number); const declaredStringIndexer = getIndexDeclarationOfSymbol(type.symbol, IndexKind.String); const stringIndexType = getIndexTypeOfType(type, IndexKind.String); const numberIndexType = getIndexTypeOfType(type, IndexKind.Number); if (stringIndexType || numberIndexType) { forEach(getPropertiesOfObjectType(type), prop => { const propType = getTypeOfSymbol(prop); checkIndexConstraintForProperty(prop, propType, type, declaredStringIndexer, stringIndexType, IndexKind.String); checkIndexConstraintForProperty(prop, propType, type, declaredNumberIndexer, numberIndexType, IndexKind.Number); }); const classDeclaration = type.symbol.valueDeclaration; if (getObjectFlags(type) & ObjectFlags.Class && isClassLike(classDeclaration)) { for (const member of classDeclaration.members) { // Only process instance properties with computed names here. // Static properties cannot be in conflict with indexers, // and properties with literal names were already checked. if (!hasSyntacticModifier(member, ModifierFlags.Static) && hasNonBindableDynamicName(member)) { const symbol = getSymbolOfNode(member); const propType = getTypeOfSymbol(symbol); checkIndexConstraintForProperty(symbol, propType, type, declaredStringIndexer, stringIndexType, IndexKind.String); checkIndexConstraintForProperty(symbol, propType, type, declaredNumberIndexer, numberIndexType, IndexKind.Number); } } } } let errorNode: Node | undefined; if (stringIndexType && numberIndexType) { errorNode = declaredNumberIndexer || declaredStringIndexer; // condition 'errorNode === undefined' may appear if types does not declare nor string neither number indexer if (!errorNode && (getObjectFlags(type) & ObjectFlags.Interface)) { const someBaseTypeHasBothIndexers = forEach(getBaseTypes(type), base => getIndexTypeOfType(base, IndexKind.String) && getIndexTypeOfType(base, IndexKind.Number)); errorNode = someBaseTypeHasBothIndexers ? undefined : type.symbol.declarations[0]; } } if (errorNode && !isTypeAssignableTo(numberIndexType!, stringIndexType!)) { // TODO: GH#18217 error(errorNode, Diagnostics.Numeric_index_type_0_is_not_assignable_to_string_index_type_1, typeToString(numberIndexType!), typeToString(stringIndexType!)); } function checkIndexConstraintForProperty( prop: Symbol, propertyType: Type, containingType: Type, indexDeclaration: Declaration | undefined, indexType: Type | undefined, indexKind: IndexKind): void { // ESSymbol properties apply to neither string nor numeric indexers. if (!indexType || isKnownSymbol(prop)) { return; } const propDeclaration = prop.valueDeclaration; const name = propDeclaration && getNameOfDeclaration(propDeclaration); if (name && isPrivateIdentifier(name)) { return; } // index is numeric and property name is not valid numeric literal if (indexKind === IndexKind.Number && !(name ? isNumericName(name) : isNumericLiteralName(prop.escapedName))) { return; } // perform property check if property or indexer is declared in 'type' // this allows us to rule out cases when both property and indexer are inherited from the base class let errorNode: Node | undefined; if (propDeclaration && name && (propDeclaration.kind === SyntaxKind.BinaryExpression || name.kind === SyntaxKind.ComputedPropertyName || prop.parent === containingType.symbol)) { errorNode = propDeclaration; } else if (indexDeclaration) { errorNode = indexDeclaration; } else if (getObjectFlags(containingType) & ObjectFlags.Interface) { // for interfaces property and indexer might be inherited from different bases // check if any base class already has both property and indexer. // check should be performed only if 'type' is the first type that brings property\indexer together const someBaseClassHasBothPropertyAndIndexer = forEach(getBaseTypes(containingType), base => getPropertyOfObjectType(base, prop.escapedName) && getIndexTypeOfType(base, indexKind)); errorNode = someBaseClassHasBothPropertyAndIndexer ? undefined : containingType.symbol.declarations[0]; } if (errorNode && !isTypeAssignableTo(propertyType, indexType)) { const errorMessage = indexKind === IndexKind.String ? Diagnostics.Property_0_of_type_1_is_not_assignable_to_string_index_type_2 : Diagnostics.Property_0_of_type_1_is_not_assignable_to_numeric_index_type_2; error(errorNode, errorMessage, symbolToString(prop), typeToString(propertyType), typeToString(indexType)); } } } function checkTypeNameIsReserved(name: Identifier, message: DiagnosticMessage): void { // TS 1.0 spec (April 2014): 3.6.1 // The predefined type keywords are reserved and cannot be used as names of user defined types. switch (name.escapedText) { case "any": case "unknown": case "number": case "bigint": case "boolean": case "string": case "symbol": case "void": case "object": error(name, message, name.escapedText as string); } } /** * The name cannot be used as 'Object' of user defined types with special target. */ function checkClassNameCollisionWithObject(name: Identifier): void { if (languageVersion === ScriptTarget.ES5 && name.escapedText === "Object" && moduleKind < ModuleKind.ES2015) { error(name, Diagnostics.Class_name_cannot_be_Object_when_targeting_ES5_with_module_0, ModuleKind[moduleKind]); // https://github.com/Microsoft/TypeScript/issues/17494 } } /** * Check each type parameter and check that type parameters have no duplicate type parameter declarations */ function checkTypeParameters(typeParameterDeclarations: readonly TypeParameterDeclaration[] | undefined) { if (typeParameterDeclarations) { let seenDefault = false; for (let i = 0; i < typeParameterDeclarations.length; i++) { const node = typeParameterDeclarations[i]; checkTypeParameter(node); if (produceDiagnostics) { if (node.default) { seenDefault = true; checkTypeParametersNotReferenced(node.default, typeParameterDeclarations, i); } else if (seenDefault) { error(node, Diagnostics.Required_type_parameters_may_not_follow_optional_type_parameters); } for (let j = 0; j < i; j++) { if (typeParameterDeclarations[j].symbol === node.symbol) { error(node.name, Diagnostics.Duplicate_identifier_0, declarationNameToString(node.name)); } } } } } } /** Check that type parameter defaults only reference previously declared type parameters */ function checkTypeParametersNotReferenced(root: TypeNode, typeParameters: readonly TypeParameterDeclaration[], index: number) { visit(root); function visit(node: Node) { if (node.kind === SyntaxKind.TypeReference) { const type = getTypeFromTypeReference(node); if (type.flags & TypeFlags.TypeParameter) { for (let i = index; i < typeParameters.length; i++) { if (type.symbol === getSymbolOfNode(typeParameters[i])) { error(node, Diagnostics.Type_parameter_defaults_can_only_reference_previously_declared_type_parameters); } } } } forEachChild(node, visit); } } /** Check that type parameter lists are identical across multiple declarations */ function checkTypeParameterListsIdentical(symbol: Symbol) { if (symbol.declarations.length === 1) { return; } const links = getSymbolLinks(symbol); if (!links.typeParametersChecked) { links.typeParametersChecked = true; const declarations = getClassOrInterfaceDeclarationsOfSymbol(symbol); if (declarations.length <= 1) { return; } const type = getDeclaredTypeOfSymbol(symbol); if (!areTypeParametersIdentical(declarations, type.localTypeParameters!)) { // Report an error on every conflicting declaration. const name = symbolToString(symbol); for (const declaration of declarations) { error(declaration.name, Diagnostics.All_declarations_of_0_must_have_identical_type_parameters, name); } } } } function areTypeParametersIdentical(declarations: readonly (ClassDeclaration | InterfaceDeclaration)[], targetParameters: TypeParameter[]) { const maxTypeArgumentCount = length(targetParameters); const minTypeArgumentCount = getMinTypeArgumentCount(targetParameters); for (const declaration of declarations) { // If this declaration has too few or too many type parameters, we report an error const sourceParameters = getEffectiveTypeParameterDeclarations(declaration); const numTypeParameters = sourceParameters.length; if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) { return false; } for (let i = 0; i < numTypeParameters; i++) { const source = sourceParameters[i]; const target = targetParameters[i]; // If the type parameter node does not have the same as the resolved type // parameter at this position, we report an error. if (source.name.escapedText !== target.symbol.escapedName) { return false; } // If the type parameter node does not have an identical constraint as the resolved // type parameter at this position, we report an error. const constraint = getEffectiveConstraintOfTypeParameter(source); const sourceConstraint = constraint && getTypeFromTypeNode(constraint); const targetConstraint = getConstraintOfTypeParameter(target); // relax check if later interface augmentation has no constraint, it's more broad and is OK to merge with // a more constrained interface (this could be generalized to a full hierarchy check, but that's maybe overkill) if (sourceConstraint && targetConstraint && !isTypeIdenticalTo(sourceConstraint, targetConstraint)) { return false; } // If the type parameter node has a default and it is not identical to the default // for the type parameter at this position, we report an error. const sourceDefault = source.default && getTypeFromTypeNode(source.default); const targetDefault = getDefaultFromTypeParameter(target); if (sourceDefault && targetDefault && !isTypeIdenticalTo(sourceDefault, targetDefault)) { return false; } } } return true; } function checkClassExpression(node: ClassExpression): Type { checkClassLikeDeclaration(node); checkNodeDeferred(node); return getTypeOfSymbol(getSymbolOfNode(node)); } function checkClassExpressionDeferred(node: ClassExpression) { forEach(node.members, checkSourceElement); registerForUnusedIdentifiersCheck(node); } function checkClassDeclaration(node: ClassDeclaration) { if (!node.name && !hasSyntacticModifier(node, ModifierFlags.Default)) { grammarErrorOnFirstToken(node, Diagnostics.A_class_declaration_without_the_default_modifier_must_have_a_name); } checkClassLikeDeclaration(node); forEach(node.members, checkSourceElement); registerForUnusedIdentifiersCheck(node); } function checkClassLikeDeclaration(node: ClassLikeDeclaration) { checkGrammarClassLikeDeclaration(node); checkDecorators(node); if (node.name) { checkTypeNameIsReserved(node.name, Diagnostics.Class_name_cannot_be_0); checkCollisionWithRequireExportsInGeneratedCode(node, node.name); checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name); if (!(node.flags & NodeFlags.Ambient)) { checkClassNameCollisionWithObject(node.name); } } checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); checkExportsOnMergedDeclarations(node); const symbol = getSymbolOfNode(node); const type = getDeclaredTypeOfSymbol(symbol); const typeWithThis = getTypeWithThisArgument(type); const staticType = getTypeOfSymbol(symbol); checkTypeParameterListsIdentical(symbol); checkFunctionOrConstructorSymbol(symbol); checkClassForDuplicateDeclarations(node); // Only check for reserved static identifiers on non-ambient context. if (!(node.flags & NodeFlags.Ambient)) { checkClassForStaticPropertyNameConflicts(node); } const baseTypeNode = getEffectiveBaseTypeNode(node); if (baseTypeNode) { forEach(baseTypeNode.typeArguments, checkSourceElement); if (languageVersion < ScriptTarget.ES2015) { checkExternalEmitHelpers(baseTypeNode.parent, ExternalEmitHelpers.Extends); } // check both @extends and extends if both are specified. const extendsNode = getClassExtendsHeritageElement(node); if (extendsNode && extendsNode !== baseTypeNode) { checkExpression(extendsNode.expression); } const baseTypes = getBaseTypes(type); if (baseTypes.length && produceDiagnostics) { const baseType = baseTypes[0]; const baseConstructorType = getBaseConstructorTypeOfClass(type); const staticBaseType = getApparentType(baseConstructorType); checkBaseTypeAccessibility(staticBaseType, baseTypeNode); checkSourceElement(baseTypeNode.expression); if (some(baseTypeNode.typeArguments)) { forEach(baseTypeNode.typeArguments, checkSourceElement); for (const constructor of getConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode)) { if (!checkTypeArgumentConstraints(baseTypeNode, constructor.typeParameters!)) { break; } } } const baseWithThis = getTypeWithThisArgument(baseType, type.thisType); if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { issueMemberSpecificError(node, typeWithThis, baseWithThis, Diagnostics.Class_0_incorrectly_extends_base_class_1); } else { // Report static side error only when instance type is assignable checkTypeAssignableTo(staticType, getTypeWithoutSignatures(staticBaseType), node.name || node, Diagnostics.Class_static_side_0_incorrectly_extends_base_class_static_side_1); } if (baseConstructorType.flags & TypeFlags.TypeVariable && !isMixinConstructorType(staticType)) { error(node.name || node, Diagnostics.A_mixin_class_must_have_a_constructor_with_a_single_rest_parameter_of_type_any); } if (!(staticBaseType.symbol && staticBaseType.symbol.flags & SymbolFlags.Class) && !(baseConstructorType.flags & TypeFlags.TypeVariable)) { // When the static base type is a "class-like" constructor function (but not actually a class), we verify // that all instantiated base constructor signatures return the same type. const constructors = getInstantiatedConstructorsForTypeArguments(staticBaseType, baseTypeNode.typeArguments, baseTypeNode); if (forEach(constructors, sig => !isJSConstructor(sig.declaration) && !isTypeIdenticalTo(getReturnTypeOfSignature(sig), baseType))) { error(baseTypeNode.expression, Diagnostics.Base_constructors_must_all_have_the_same_return_type); } } checkKindsOfPropertyMemberOverrides(type, baseType); } } const implementedTypeNodes = getEffectiveImplementsTypeNodes(node); if (implementedTypeNodes) { for (const typeRefNode of implementedTypeNodes) { if (!isEntityNameExpression(typeRefNode.expression)) { error(typeRefNode.expression, Diagnostics.A_class_can_only_implement_an_identifier_Slashqualified_name_with_optional_type_arguments); } checkTypeReferenceNode(typeRefNode); if (produceDiagnostics) { const t = getReducedType(getTypeFromTypeNode(typeRefNode)); if (t !== errorType) { if (isValidBaseType(t)) { const genericDiag = t.symbol && t.symbol.flags & SymbolFlags.Class ? Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass : Diagnostics.Class_0_incorrectly_implements_interface_1; const baseWithThis = getTypeWithThisArgument(t, type.thisType); if (!checkTypeAssignableTo(typeWithThis, baseWithThis, /*errorNode*/ undefined)) { issueMemberSpecificError(node, typeWithThis, baseWithThis, genericDiag); } } else { error(typeRefNode, Diagnostics.A_class_can_only_implement_an_object_type_or_intersection_of_object_types_with_statically_known_members); } } } } } if (produceDiagnostics) { checkIndexConstraints(type); checkTypeForDuplicateIndexSignatures(node); checkPropertyInitialization(node); } } function issueMemberSpecificError(node: ClassLikeDeclaration, typeWithThis: Type, baseWithThis: Type, broadDiag: DiagnosticMessage) { // iterate over all implemented properties and issue errors on each one which isn't compatible, rather than the class as a whole, if possible let issuedMemberError = false; for (const member of node.members) { if (hasStaticModifier(member)) { continue; } const declaredProp = member.name && getSymbolAtLocation(member.name) || getSymbolAtLocation(member); if (declaredProp) { const prop = getPropertyOfType(typeWithThis, declaredProp.escapedName); const baseProp = getPropertyOfType(baseWithThis, declaredProp.escapedName); if (prop && baseProp) { const rootChain = () => chainDiagnosticMessages( /*details*/ undefined, Diagnostics.Property_0_in_type_1_is_not_assignable_to_the_same_property_in_base_type_2, symbolToString(declaredProp), typeToString(typeWithThis), typeToString(baseWithThis) ); if (!checkTypeAssignableTo(getTypeOfSymbol(prop), getTypeOfSymbol(baseProp), member.name || member, /*message*/ undefined, rootChain)) { issuedMemberError = true; } } } } if (!issuedMemberError) { // check again with diagnostics to generate a less-specific error checkTypeAssignableTo(typeWithThis, baseWithThis, node.name || node, broadDiag); } } function checkBaseTypeAccessibility(type: Type, node: ExpressionWithTypeArguments) { const signatures = getSignaturesOfType(type, SignatureKind.Construct); if (signatures.length) { const declaration = signatures[0].declaration; if (declaration && hasEffectiveModifier(declaration, ModifierFlags.Private)) { const typeClassDeclaration = getClassLikeDeclarationOfSymbol(type.symbol)!; if (!isNodeWithinClass(node, typeClassDeclaration)) { error(node, Diagnostics.Cannot_extend_a_class_0_Class_constructor_is_marked_as_private, getFullyQualifiedName(type.symbol)); } } } } function getTargetSymbol(s: Symbol) { // if symbol is instantiated its flags are not copied from the 'target' // so we'll need to get back original 'target' symbol to work with correct set of flags return getCheckFlags(s) & CheckFlags.Instantiated ? (s).target! : s; } function getClassOrInterfaceDeclarationsOfSymbol(symbol: Symbol) { return filter(symbol.declarations, (d: Declaration): d is ClassDeclaration | InterfaceDeclaration => d.kind === SyntaxKind.ClassDeclaration || d.kind === SyntaxKind.InterfaceDeclaration); } function checkKindsOfPropertyMemberOverrides(type: InterfaceType, baseType: BaseType): void { // TypeScript 1.0 spec (April 2014): 8.2.3 // A derived class inherits all members from its base class it doesn't override. // Inheritance means that a derived class implicitly contains all non - overridden members of the base class. // Both public and private property members are inherited, but only public property members can be overridden. // A property member in a derived class is said to override a property member in a base class // when the derived class property member has the same name and kind(instance or static) // as the base class property member. // The type of an overriding property member must be assignable(section 3.8.4) // to the type of the overridden property member, or otherwise a compile - time error occurs. // Base class instance member functions can be overridden by derived class instance member functions, // but not by other kinds of members. // Base class instance member variables and accessors can be overridden by // derived class instance member variables and accessors, but not by other kinds of members. // NOTE: assignability is checked in checkClassDeclaration const baseProperties = getPropertiesOfType(baseType); basePropertyCheck: for (const baseProperty of baseProperties) { const base = getTargetSymbol(baseProperty); if (base.flags & SymbolFlags.Prototype) { continue; } const baseSymbol = getPropertyOfObjectType(type, base.escapedName); if (!baseSymbol) { continue; } const derived = getTargetSymbol(baseSymbol); const baseDeclarationFlags = getDeclarationModifierFlagsFromSymbol(base); Debug.assert(!!derived, "derived should point to something, even if it is the base class' declaration."); // In order to resolve whether the inherited method was overridden in the base class or not, // we compare the Symbols obtained. Since getTargetSymbol returns the symbol on the *uninstantiated* // type declaration, derived and base resolve to the same symbol even in the case of generic classes. if (derived === base) { // derived class inherits base without override/redeclaration const derivedClassDecl = getClassLikeDeclarationOfSymbol(type.symbol)!; // It is an error to inherit an abstract member without implementing it or being declared abstract. // If there is no declaration for the derived class (as in the case of class expressions), // then the class cannot be declared abstract. if (baseDeclarationFlags & ModifierFlags.Abstract && (!derivedClassDecl || !hasSyntacticModifier(derivedClassDecl, ModifierFlags.Abstract))) { // Searches other base types for a declaration that would satisfy the inherited abstract member. // (The class may have more than one base type via declaration merging with an interface with the // same name.) for (const otherBaseType of getBaseTypes(type)) { if (otherBaseType === baseType) continue; const baseSymbol = getPropertyOfObjectType(otherBaseType, base.escapedName); const derivedElsewhere = baseSymbol && getTargetSymbol(baseSymbol); if (derivedElsewhere && derivedElsewhere !== base) { continue basePropertyCheck; } } if (derivedClassDecl.kind === SyntaxKind.ClassExpression) { error(derivedClassDecl, Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1, symbolToString(baseProperty), typeToString(baseType)); } else { error(derivedClassDecl, Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2, typeToString(type), symbolToString(baseProperty), typeToString(baseType)); } } } else { // derived overrides base. const derivedDeclarationFlags = getDeclarationModifierFlagsFromSymbol(derived); if (baseDeclarationFlags & ModifierFlags.Private || derivedDeclarationFlags & ModifierFlags.Private) { // either base or derived property is private - not override, skip it continue; } let errorMessage: DiagnosticMessage; const basePropertyFlags = base.flags & SymbolFlags.PropertyOrAccessor; const derivedPropertyFlags = derived.flags & SymbolFlags.PropertyOrAccessor; if (basePropertyFlags && derivedPropertyFlags) { // property/accessor is overridden with property/accessor if (baseDeclarationFlags & ModifierFlags.Abstract && !(base.valueDeclaration && isPropertyDeclaration(base.valueDeclaration) && base.valueDeclaration.initializer) || base.valueDeclaration && base.valueDeclaration.parent.kind === SyntaxKind.InterfaceDeclaration || derived.valueDeclaration && isBinaryExpression(derived.valueDeclaration)) { // when the base property is abstract or from an interface, base/derived flags don't need to match // same when the derived property is from an assignment continue; } const overriddenInstanceProperty = basePropertyFlags !== SymbolFlags.Property && derivedPropertyFlags === SymbolFlags.Property; const overriddenInstanceAccessor = basePropertyFlags === SymbolFlags.Property && derivedPropertyFlags !== SymbolFlags.Property; if (overriddenInstanceProperty || overriddenInstanceAccessor) { const errorMessage = overriddenInstanceProperty ? Diagnostics._0_is_defined_as_an_accessor_in_class_1_but_is_overridden_here_in_2_as_an_instance_property : Diagnostics._0_is_defined_as_a_property_in_class_1_but_is_overridden_here_in_2_as_an_accessor; error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType), typeToString(type)); } else if (compilerOptions.useDefineForClassFields) { const uninitialized = find(derived.declarations, d => d.kind === SyntaxKind.PropertyDeclaration && !(d as PropertyDeclaration).initializer); if (uninitialized && !(derived.flags & SymbolFlags.Transient) && !(baseDeclarationFlags & ModifierFlags.Abstract) && !(derivedDeclarationFlags & ModifierFlags.Abstract) && !derived.declarations.some(d => !!(d.flags & NodeFlags.Ambient))) { const constructor = findConstructorDeclaration(getClassLikeDeclarationOfSymbol(type.symbol)!); const propName = (uninitialized as PropertyDeclaration).name; if ((uninitialized as PropertyDeclaration).exclamationToken || !constructor || !isIdentifier(propName) || !strictNullChecks || !isPropertyInitializedInConstructor(propName, type, constructor)) { const errorMessage = Diagnostics.Property_0_will_overwrite_the_base_property_in_1_If_this_is_intentional_add_an_initializer_Otherwise_add_a_declare_modifier_or_remove_the_redundant_declaration; error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType)); } } } // correct case continue; } else if (isPrototypeProperty(base)) { if (isPrototypeProperty(derived) || derived.flags & SymbolFlags.Property) { // method is overridden with method or property -- correct case continue; } else { Debug.assert(!!(derived.flags & SymbolFlags.Accessor)); errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor; } } else if (base.flags & SymbolFlags.Accessor) { errorMessage = Diagnostics.Class_0_defines_instance_member_accessor_1_but_extended_class_2_defines_it_as_instance_member_function; } else { errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function; } error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, typeToString(baseType), symbolToString(base), typeToString(type)); } } } function getNonInterhitedProperties(type: InterfaceType, baseTypes: BaseType[], properties: Symbol[]) { if (!length(baseTypes)) { return properties; } const seen = new Map<__String, Symbol>(); forEach(properties, p => { seen.set(p.escapedName, p); }); for (const base of baseTypes) { const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType)); for (const prop of properties) { const existing = seen.get(prop.escapedName); if (existing && !isPropertyIdenticalTo(existing, prop)) { seen.delete(prop.escapedName); } } } return arrayFrom(seen.values()); } function checkInheritedPropertiesAreIdentical(type: InterfaceType, typeNode: Node): boolean { const baseTypes = getBaseTypes(type); if (baseTypes.length < 2) { return true; } interface InheritanceInfoMap { prop: Symbol; containingType: Type; } const seen = new Map<__String, InheritanceInfoMap>(); forEach(resolveDeclaredMembers(type).declaredProperties, p => { seen.set(p.escapedName, { prop: p, containingType: type }); }); let ok = true; for (const base of baseTypes) { const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType)); for (const prop of properties) { const existing = seen.get(prop.escapedName); if (!existing) { seen.set(prop.escapedName, { prop, containingType: base }); } else { const isInheritedProperty = existing.containingType !== type; if (isInheritedProperty && !isPropertyIdenticalTo(existing.prop, prop)) { ok = false; const typeName1 = typeToString(existing.containingType); const typeName2 = typeToString(base); let errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Named_property_0_of_types_1_and_2_are_not_identical, symbolToString(prop), typeName1, typeName2); errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Interface_0_cannot_simultaneously_extend_types_1_and_2, typeToString(type), typeName1, typeName2); diagnostics.add(createDiagnosticForNodeFromMessageChain(typeNode, errorInfo)); } } } } return ok; } function checkPropertyInitialization(node: ClassLikeDeclaration) { if (!strictNullChecks || !strictPropertyInitialization || node.flags & NodeFlags.Ambient) { return; } const constructor = findConstructorDeclaration(node); for (const member of node.members) { if (getEffectiveModifierFlags(member) & ModifierFlags.Ambient) { continue; } if (isInstancePropertyWithoutInitializer(member)) { const propName = (member).name; if (isIdentifier(propName) || isPrivateIdentifier(propName)) { const type = getTypeOfSymbol(getSymbolOfNode(member)); if (!(type.flags & TypeFlags.AnyOrUnknown || getFalsyFlags(type) & TypeFlags.Undefined)) { if (!constructor || !isPropertyInitializedInConstructor(propName, type, constructor)) { error(member.name, Diagnostics.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_the_constructor, declarationNameToString(propName)); } } } } } } function isInstancePropertyWithoutInitializer(node: Node) { return node.kind === SyntaxKind.PropertyDeclaration && !hasSyntacticModifier(node, ModifierFlags.Static | ModifierFlags.Abstract) && !(node).exclamationToken && !(node).initializer; } function isPropertyInitializedInConstructor(propName: Identifier | PrivateIdentifier, propType: Type, constructor: ConstructorDeclaration) { const reference = factory.createPropertyAccessExpression(factory.createThis(), propName); setParent(reference.expression, reference); setParent(reference, constructor); reference.flowNode = constructor.returnFlowNode; const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); return !(getFalsyFlags(flowType) & TypeFlags.Undefined); } function checkInterfaceDeclaration(node: InterfaceDeclaration) { // Grammar checking if (!checkGrammarDecoratorsAndModifiers(node)) checkGrammarInterfaceDeclaration(node); checkTypeParameters(node.typeParameters); if (produceDiagnostics) { checkTypeNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0); checkExportsOnMergedDeclarations(node); const symbol = getSymbolOfNode(node); checkTypeParameterListsIdentical(symbol); // Only check this symbol once const firstInterfaceDecl = getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration); if (node === firstInterfaceDecl) { const type = getDeclaredTypeOfSymbol(symbol); const typeWithThis = getTypeWithThisArgument(type); // run subsequent checks only if first set succeeded if (checkInheritedPropertiesAreIdentical(type, node.name)) { for (const baseType of getBaseTypes(type)) { checkTypeAssignableTo(typeWithThis, getTypeWithThisArgument(baseType, type.thisType), node.name, Diagnostics.Interface_0_incorrectly_extends_interface_1); } checkIndexConstraints(type); } } checkObjectTypeForDuplicateDeclarations(node); } forEach(getInterfaceBaseTypeNodes(node), heritageElement => { if (!isEntityNameExpression(heritageElement.expression)) { error(heritageElement.expression, Diagnostics.An_interface_can_only_extend_an_identifier_Slashqualified_name_with_optional_type_arguments); } checkTypeReferenceNode(heritageElement); }); forEach(node.members, checkSourceElement); if (produceDiagnostics) { checkTypeForDuplicateIndexSignatures(node); registerForUnusedIdentifiersCheck(node); } } function checkTypeAliasDeclaration(node: TypeAliasDeclaration) { // Grammar checking checkGrammarDecoratorsAndModifiers(node); checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); checkExportsOnMergedDeclarations(node); checkTypeParameters(node.typeParameters); if (node.type.kind === SyntaxKind.IntrinsicKeyword) { if (!intrinsicTypeKinds.has(node.name.escapedText as string) || length(node.typeParameters) !== 1) { error(node.type, Diagnostics.The_intrinsic_keyword_can_only_be_used_to_declare_compiler_provided_intrinsic_types); } } else { checkSourceElement(node.type); registerForUnusedIdentifiersCheck(node); } } function computeEnumMemberValues(node: EnumDeclaration) { const nodeLinks = getNodeLinks(node); if (!(nodeLinks.flags & NodeCheckFlags.EnumValuesComputed)) { nodeLinks.flags |= NodeCheckFlags.EnumValuesComputed; let autoValue: number | undefined = 0; for (const member of node.members) { const value = computeMemberValue(member, autoValue); getNodeLinks(member).enumMemberValue = value; autoValue = typeof value === "number" ? value + 1 : undefined; } } } function computeMemberValue(member: EnumMember, autoValue: number | undefined) { if (isComputedNonLiteralName(member.name)) { error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums); } else { const text = getTextOfPropertyName(member.name); if (isNumericLiteralName(text) && !isInfinityOrNaNString(text)) { error(member.name, Diagnostics.An_enum_member_cannot_have_a_numeric_name); } } if (member.initializer) { return computeConstantValue(member); } // In ambient non-const numeric enum declarations, enum members without initializers are // considered computed members (as opposed to having auto-incremented values). if (member.parent.flags & NodeFlags.Ambient && !isEnumConst(member.parent) && getEnumKind(getSymbolOfNode(member.parent)) === EnumKind.Numeric) { return undefined; } // If the member declaration specifies no value, the member is considered a constant enum member. // If the member is the first member in the enum declaration, it is assigned the value zero. // Otherwise, it is assigned the value of the immediately preceding member plus one, and an error // occurs if the immediately preceding member is not a constant enum member. if (autoValue !== undefined) { return autoValue; } error(member.name, Diagnostics.Enum_member_must_have_initializer); return undefined; } function computeConstantValue(member: EnumMember): string | number | undefined { const enumKind = getEnumKind(getSymbolOfNode(member.parent)); const isConstEnum = isEnumConst(member.parent); const initializer = member.initializer!; const value = enumKind === EnumKind.Literal && !isLiteralEnumMember(member) ? undefined : evaluate(initializer); if (value !== undefined) { if (isConstEnum && typeof value === "number" && !isFinite(value)) { error(initializer, isNaN(value) ? Diagnostics.const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN : Diagnostics.const_enum_member_initializer_was_evaluated_to_a_non_finite_value); } } else if (enumKind === EnumKind.Literal) { error(initializer, Diagnostics.Computed_values_are_not_permitted_in_an_enum_with_string_valued_members); return 0; } else if (isConstEnum) { error(initializer, Diagnostics.const_enum_member_initializers_can_only_contain_literal_values_and_other_computed_enum_values); } else if (member.parent.flags & NodeFlags.Ambient) { error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression); } else { // Only here do we need to check that the initializer is assignable to the enum type. const source = checkExpression(initializer); if (!isTypeAssignableToKind(source, TypeFlags.NumberLike)) { error(initializer, Diagnostics.Only_numeric_enums_can_have_computed_members_but_this_expression_has_type_0_If_you_do_not_need_exhaustiveness_checks_consider_using_an_object_literal_instead, typeToString(source)); } else { checkTypeAssignableTo(source, getDeclaredTypeOfSymbol(getSymbolOfNode(member.parent)), initializer, /*headMessage*/ undefined); } } return value; function evaluate(expr: Expression): string | number | undefined { switch (expr.kind) { case SyntaxKind.PrefixUnaryExpression: const value = evaluate((expr).operand); if (typeof value === "number") { switch ((expr).operator) { case SyntaxKind.PlusToken: return value; case SyntaxKind.MinusToken: return -value; case SyntaxKind.TildeToken: return ~value; } } break; case SyntaxKind.BinaryExpression: const left = evaluate((expr).left); const right = evaluate((expr).right); if (typeof left === "number" && typeof right === "number") { switch ((expr).operatorToken.kind) { case SyntaxKind.BarToken: return left | right; case SyntaxKind.AmpersandToken: return left & right; case SyntaxKind.GreaterThanGreaterThanToken: return left >> right; case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: return left >>> right; case SyntaxKind.LessThanLessThanToken: return left << right; case SyntaxKind.CaretToken: return left ^ right; case SyntaxKind.AsteriskToken: return left * right; case SyntaxKind.SlashToken: return left / right; case SyntaxKind.PlusToken: return left + right; case SyntaxKind.MinusToken: return left - right; case SyntaxKind.PercentToken: return left % right; case SyntaxKind.AsteriskAsteriskToken: return left ** right; } } else if (typeof left === "string" && typeof right === "string" && (expr).operatorToken.kind === SyntaxKind.PlusToken) { return left + right; } break; case SyntaxKind.StringLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: return (expr).text; case SyntaxKind.NumericLiteral: checkGrammarNumericLiteral(expr); return +(expr).text; case SyntaxKind.ParenthesizedExpression: return evaluate((expr).expression); case SyntaxKind.Identifier: const identifier = expr; if (isInfinityOrNaNString(identifier.escapedText)) { return +(identifier.escapedText); } return nodeIsMissing(expr) ? 0 : evaluateEnumMember(expr, getSymbolOfNode(member.parent), identifier.escapedText); case SyntaxKind.ElementAccessExpression: case SyntaxKind.PropertyAccessExpression: const ex = expr; if (isConstantMemberAccess(ex)) { const type = getTypeOfExpression(ex.expression); if (type.symbol && type.symbol.flags & SymbolFlags.Enum) { let name: __String; if (ex.kind === SyntaxKind.PropertyAccessExpression) { name = ex.name.escapedText; } else { name = escapeLeadingUnderscores(cast(ex.argumentExpression, isLiteralExpression).text); } return evaluateEnumMember(expr, type.symbol, name); } } break; } return undefined; } function evaluateEnumMember(expr: Expression, enumSymbol: Symbol, name: __String) { const memberSymbol = enumSymbol.exports!.get(name); if (memberSymbol) { const declaration = memberSymbol.valueDeclaration; if (declaration !== member) { if (isBlockScopedNameDeclaredBeforeUse(declaration, member)) { return getEnumMemberValue(declaration as EnumMember); } error(expr, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums); return 0; } else { error(expr, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(memberSymbol)); } } return undefined; } } function isConstantMemberAccess(node: Expression): boolean { return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PropertyAccessExpression && isConstantMemberAccess((node).expression) || node.kind === SyntaxKind.ElementAccessExpression && isConstantMemberAccess((node).expression) && isStringLiteralLike((node).argumentExpression); } function checkEnumDeclaration(node: EnumDeclaration) { if (!produceDiagnostics) { return; } // Grammar checking checkGrammarDecoratorsAndModifiers(node); checkTypeNameIsReserved(node.name, Diagnostics.Enum_name_cannot_be_0); checkCollisionWithRequireExportsInGeneratedCode(node, node.name); checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name); checkExportsOnMergedDeclarations(node); node.members.forEach(checkEnumMember); computeEnumMemberValues(node); // Spec 2014 - Section 9.3: // It isn't possible for one enum declaration to continue the automatic numbering sequence of another, // and when an enum type has multiple declarations, only one declaration is permitted to omit a value // for the first member. // // Only perform this check once per symbol const enumSymbol = getSymbolOfNode(node); const firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind); if (node === firstDeclaration) { if (enumSymbol.declarations.length > 1) { const enumIsConst = isEnumConst(node); // check that const is placed\omitted on all enum declarations forEach(enumSymbol.declarations, decl => { if (isEnumDeclaration(decl) && isEnumConst(decl) !== enumIsConst) { error(getNameOfDeclaration(decl), Diagnostics.Enum_declarations_must_all_be_const_or_non_const); } }); } let seenEnumMissingInitialInitializer = false; forEach(enumSymbol.declarations, declaration => { // return true if we hit a violation of the rule, false otherwise if (declaration.kind !== SyntaxKind.EnumDeclaration) { return false; } const enumDeclaration = declaration; if (!enumDeclaration.members.length) { return false; } const firstEnumMember = enumDeclaration.members[0]; if (!firstEnumMember.initializer) { if (seenEnumMissingInitialInitializer) { error(firstEnumMember.name, Diagnostics.In_an_enum_with_multiple_declarations_only_one_declaration_can_omit_an_initializer_for_its_first_enum_element); } else { seenEnumMissingInitialInitializer = true; } } }); } } function checkEnumMember(node: EnumMember) { if (isPrivateIdentifier(node.name)) { error(node, Diagnostics.An_enum_member_cannot_be_named_with_a_private_identifier); } } function getFirstNonAmbientClassOrFunctionDeclaration(symbol: Symbol): Declaration | undefined { const declarations = symbol.declarations; for (const declaration of declarations) { if ((declaration.kind === SyntaxKind.ClassDeclaration || (declaration.kind === SyntaxKind.FunctionDeclaration && nodeIsPresent((declaration).body))) && !(declaration.flags & NodeFlags.Ambient)) { return declaration; } } return undefined; } function inSameLexicalScope(node1: Node, node2: Node) { const container1 = getEnclosingBlockScopeContainer(node1); const container2 = getEnclosingBlockScopeContainer(node2); if (isGlobalSourceFile(container1)) { return isGlobalSourceFile(container2); } else if (isGlobalSourceFile(container2)) { return false; } else { return container1 === container2; } } function checkModuleDeclaration(node: ModuleDeclaration) { if (produceDiagnostics) { // Grammar checking const isGlobalAugmentation = isGlobalScopeAugmentation(node); const inAmbientContext = node.flags & NodeFlags.Ambient; if (isGlobalAugmentation && !inAmbientContext) { error(node.name, Diagnostics.Augmentations_for_the_global_scope_should_have_declare_modifier_unless_they_appear_in_already_ambient_context); } const isAmbientExternalModule = isAmbientModule(node); const contextErrorMessage = isAmbientExternalModule ? Diagnostics.An_ambient_module_declaration_is_only_allowed_at_the_top_level_in_a_file : Diagnostics.A_namespace_declaration_is_only_allowed_in_a_namespace_or_module; if (checkGrammarModuleElementContext(node, contextErrorMessage)) { // If we hit a module declaration in an illegal context, just bail out to avoid cascading errors. return; } if (!checkGrammarDecoratorsAndModifiers(node)) { if (!inAmbientContext && node.name.kind === SyntaxKind.StringLiteral) { grammarErrorOnNode(node.name, Diagnostics.Only_ambient_modules_can_use_quoted_names); } } if (isIdentifier(node.name)) { checkCollisionWithRequireExportsInGeneratedCode(node, node.name); checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name); } checkExportsOnMergedDeclarations(node); const symbol = getSymbolOfNode(node); // The following checks only apply on a non-ambient instantiated module declaration. if (symbol.flags & SymbolFlags.ValueModule && !inAmbientContext && symbol.declarations.length > 1 && isInstantiatedModule(node, !!compilerOptions.preserveConstEnums || !!compilerOptions.isolatedModules)) { const firstNonAmbientClassOrFunc = getFirstNonAmbientClassOrFunctionDeclaration(symbol); if (firstNonAmbientClassOrFunc) { if (getSourceFileOfNode(node) !== getSourceFileOfNode(firstNonAmbientClassOrFunc)) { error(node.name, Diagnostics.A_namespace_declaration_cannot_be_in_a_different_file_from_a_class_or_function_with_which_it_is_merged); } else if (node.pos < firstNonAmbientClassOrFunc.pos) { error(node.name, Diagnostics.A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged); } } // if the module merges with a class declaration in the same lexical scope, // we need to track this to ensure the correct emit. const mergedClass = getDeclarationOfKind(symbol, SyntaxKind.ClassDeclaration); if (mergedClass && inSameLexicalScope(node, mergedClass)) { getNodeLinks(node).flags |= NodeCheckFlags.LexicalModuleMergesWithClass; } } if (isAmbientExternalModule) { if (isExternalModuleAugmentation(node)) { // body of the augmentation should be checked for consistency only if augmentation was applied to its target (either global scope or module) // otherwise we'll be swamped in cascading errors. // We can detect if augmentation was applied using following rules: // - augmentation for a global scope is always applied // - augmentation for some external module is applied if symbol for augmentation is merged (it was combined with target module). const checkBody = isGlobalAugmentation || (getSymbolOfNode(node).flags & SymbolFlags.Transient); if (checkBody && node.body) { for (const statement of node.body.statements) { checkModuleAugmentationElement(statement, isGlobalAugmentation); } } } else if (isGlobalSourceFile(node.parent)) { if (isGlobalAugmentation) { error(node.name, Diagnostics.Augmentations_for_the_global_scope_can_only_be_directly_nested_in_external_modules_or_ambient_module_declarations); } else if (isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(node.name))) { error(node.name, Diagnostics.Ambient_module_declaration_cannot_specify_relative_module_name); } } else { if (isGlobalAugmentation) { error(node.name, Diagnostics.Augmentations_for_the_global_scope_can_only_be_directly_nested_in_external_modules_or_ambient_module_declarations); } else { // Node is not an augmentation and is not located on the script level. // This means that this is declaration of ambient module that is located in other module or namespace which is prohibited. error(node.name, Diagnostics.Ambient_modules_cannot_be_nested_in_other_modules_or_namespaces); } } } } if (node.body) { checkSourceElement(node.body); if (!isGlobalScopeAugmentation(node)) { registerForUnusedIdentifiersCheck(node); } } } function checkModuleAugmentationElement(node: Node, isGlobalAugmentation: boolean): void { switch (node.kind) { case SyntaxKind.VariableStatement: // error each individual name in variable statement instead of marking the entire variable statement for (const decl of (node).declarationList.declarations) { checkModuleAugmentationElement(decl, isGlobalAugmentation); } break; case SyntaxKind.ExportAssignment: case SyntaxKind.ExportDeclaration: grammarErrorOnFirstToken(node, Diagnostics.Exports_and_export_assignments_are_not_permitted_in_module_augmentations); break; case SyntaxKind.ImportEqualsDeclaration: case SyntaxKind.ImportDeclaration: grammarErrorOnFirstToken(node, Diagnostics.Imports_are_not_permitted_in_module_augmentations_Consider_moving_them_to_the_enclosing_external_module); break; case SyntaxKind.BindingElement: case SyntaxKind.VariableDeclaration: const name = (node).name; if (isBindingPattern(name)) { for (const el of name.elements) { // mark individual names in binding pattern checkModuleAugmentationElement(el, isGlobalAugmentation); } break; } // falls through case SyntaxKind.ClassDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.FunctionDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.ModuleDeclaration: case SyntaxKind.TypeAliasDeclaration: if (isGlobalAugmentation) { return; } const symbol = getSymbolOfNode(node); if (symbol) { // module augmentations cannot introduce new names on the top level scope of the module // this is done it two steps // 1. quick check - if symbol for node is not merged - this is local symbol to this augmentation - report error // 2. main check - report error if value declaration of the parent symbol is module augmentation) let reportError = !(symbol.flags & SymbolFlags.Transient); if (!reportError) { // symbol should not originate in augmentation reportError = !!symbol.parent && isExternalModuleAugmentation(symbol.parent.declarations[0]); } } break; } } function getFirstNonModuleExportsIdentifier(node: EntityNameOrEntityNameExpression): Identifier { switch (node.kind) { case SyntaxKind.Identifier: return node; case SyntaxKind.QualifiedName: do { node = node.left; } while (node.kind !== SyntaxKind.Identifier); return node; case SyntaxKind.PropertyAccessExpression: do { if (isModuleExportsAccessExpression(node.expression) && !isPrivateIdentifier(node.name)) { return node.name; } node = node.expression; } while (node.kind !== SyntaxKind.Identifier); return node; } } function checkExternalImportOrExportDeclaration(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): boolean { const moduleName = getExternalModuleName(node); if (!moduleName || nodeIsMissing(moduleName)) { // Should be a parse error. return false; } if (!isStringLiteral(moduleName)) { error(moduleName, Diagnostics.String_literal_expected); return false; } const inAmbientExternalModule = node.parent.kind === SyntaxKind.ModuleBlock && isAmbientModule(node.parent.parent); if (node.parent.kind !== SyntaxKind.SourceFile && !inAmbientExternalModule) { error(moduleName, node.kind === SyntaxKind.ExportDeclaration ? Diagnostics.Export_declarations_are_not_permitted_in_a_namespace : Diagnostics.Import_declarations_in_a_namespace_cannot_reference_a_module); return false; } if (inAmbientExternalModule && isExternalModuleNameRelative(moduleName.text)) { // we have already reported errors on top level imports/exports in external module augmentations in checkModuleDeclaration // no need to do this again. if (!isTopLevelInExternalModuleAugmentation(node)) { // TypeScript 1.0 spec (April 2013): 12.1.6 // An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference // other external modules only through top - level external module names. // Relative external module names are not permitted. error(node, Diagnostics.Import_or_export_declaration_in_an_ambient_module_declaration_cannot_reference_module_through_relative_module_name); return false; } } return true; } function checkAliasSymbol(node: ImportEqualsDeclaration | VariableDeclaration | ImportClause | NamespaceImport | ImportSpecifier | ExportSpecifier | NamespaceExport) { let symbol = getSymbolOfNode(node); const target = resolveAlias(symbol); if (target !== unknownSymbol) { // For external modules, `symbol` represents the local symbol for an alias. // This local symbol will merge any other local declarations (excluding other aliases) // and symbol.flags will contains combined representation for all merged declaration. // Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have, // otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export* // in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names). symbol = getMergedSymbol(symbol.exportSymbol || symbol); const excludedMeanings = (symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) | (symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) | (symbol.flags & SymbolFlags.Namespace ? SymbolFlags.Namespace : 0); if (target.flags & excludedMeanings) { const message = node.kind === SyntaxKind.ExportSpecifier ? Diagnostics.Export_declaration_conflicts_with_exported_declaration_of_0 : Diagnostics.Import_declaration_conflicts_with_local_declaration_of_0; error(node, message, symbolToString(symbol)); } // Don't allow to re-export something with no value side when `--isolatedModules` is set. if (compilerOptions.isolatedModules && node.kind === SyntaxKind.ExportSpecifier && !node.parent.parent.isTypeOnly && !(target.flags & SymbolFlags.Value) && !(node.flags & NodeFlags.Ambient)) { error(node, Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type); } if (isImportSpecifier(node) && every(target.declarations, d => !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated))) { errorOrSuggestion(/* isError */ false, node.name, Diagnostics._0_is_deprecated, symbol.escapedName as string); } } } function checkImportBinding(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier) { checkCollisionWithRequireExportsInGeneratedCode(node, node.name!); checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name!); checkAliasSymbol(node); if (node.kind === SyntaxKind.ImportSpecifier && idText(node.propertyName || node.name) === "default" && compilerOptions.esModuleInterop && moduleKind !== ModuleKind.System && moduleKind < ModuleKind.ES2015) { checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); } } function checkImportDeclaration(node: ImportDeclaration) { if (checkGrammarModuleElementContext(node, Diagnostics.An_import_declaration_can_only_be_used_in_a_namespace_or_module)) { // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. return; } if (!checkGrammarDecoratorsAndModifiers(node) && hasEffectiveModifiers(node)) { grammarErrorOnFirstToken(node, Diagnostics.An_import_declaration_cannot_have_modifiers); } if (checkExternalImportOrExportDeclaration(node)) { const importClause = node.importClause; if (importClause && !checkGrammarImportClause(importClause)) { if (importClause.name) { checkImportBinding(importClause); } if (importClause.namedBindings) { if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { checkImportBinding(importClause.namedBindings); if (moduleKind !== ModuleKind.System && moduleKind < ModuleKind.ES2015 && compilerOptions.esModuleInterop) { // import * as ns from "foo"; checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); } } else { const moduleExisted = resolveExternalModuleName(node, node.moduleSpecifier); if (moduleExisted) { forEach(importClause.namedBindings.elements, checkImportBinding); } } } } } } function checkImportEqualsDeclaration(node: ImportEqualsDeclaration) { if (checkGrammarModuleElementContext(node, Diagnostics.An_import_declaration_can_only_be_used_in_a_namespace_or_module)) { // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. return; } checkGrammarDecoratorsAndModifiers(node); if (isInternalModuleImportEqualsDeclaration(node) || checkExternalImportOrExportDeclaration(node)) { checkImportBinding(node); if (hasSyntacticModifier(node, ModifierFlags.Export)) { markExportAsReferenced(node); } if (node.moduleReference.kind !== SyntaxKind.ExternalModuleReference) { const target = resolveAlias(getSymbolOfNode(node)); if (target !== unknownSymbol) { if (target.flags & SymbolFlags.Value) { // Target is a value symbol, check that it is not hidden by a local declaration with the same name const moduleName = getFirstIdentifier(node.moduleReference); if (!(resolveEntityName(moduleName, SymbolFlags.Value | SymbolFlags.Namespace)!.flags & SymbolFlags.Namespace)) { error(moduleName, Diagnostics.Module_0_is_hidden_by_a_local_declaration_with_the_same_name, declarationNameToString(moduleName)); } } if (target.flags & SymbolFlags.Type) { checkTypeNameIsReserved(node.name, Diagnostics.Import_name_cannot_be_0); } } } else { if (moduleKind >= ModuleKind.ES2015 && !(node.flags & NodeFlags.Ambient)) { // Import equals declaration is deprecated in es6 or above grammarErrorOnNode(node, Diagnostics.Import_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_import_Asterisk_as_ns_from_mod_import_a_from_mod_import_d_from_mod_or_another_module_format_instead); } } } } function checkExportDeclaration(node: ExportDeclaration) { if (checkGrammarModuleElementContext(node, Diagnostics.An_export_declaration_can_only_be_used_in_a_module)) { // If we hit an export in an illegal context, just bail out to avoid cascading errors. return; } if (!checkGrammarDecoratorsAndModifiers(node) && hasEffectiveModifiers(node)) { grammarErrorOnFirstToken(node, Diagnostics.An_export_declaration_cannot_have_modifiers); } if (node.moduleSpecifier && node.exportClause && isNamedExports(node.exportClause) && length(node.exportClause.elements) && languageVersion === ScriptTarget.ES3) { checkExternalEmitHelpers(node, ExternalEmitHelpers.CreateBinding); } checkGrammarExportDeclaration(node); if (!node.moduleSpecifier || checkExternalImportOrExportDeclaration(node)) { if (node.exportClause && !isNamespaceExport(node.exportClause)) { // export { x, y } // export { x, y } from "foo" forEach(node.exportClause.elements, checkExportSpecifier); const inAmbientExternalModule = node.parent.kind === SyntaxKind.ModuleBlock && isAmbientModule(node.parent.parent); const inAmbientNamespaceDeclaration = !inAmbientExternalModule && node.parent.kind === SyntaxKind.ModuleBlock && !node.moduleSpecifier && node.flags & NodeFlags.Ambient; if (node.parent.kind !== SyntaxKind.SourceFile && !inAmbientExternalModule && !inAmbientNamespaceDeclaration) { error(node, Diagnostics.Export_declarations_are_not_permitted_in_a_namespace); } } else { // export * from "foo" // export * as ns from "foo"; const moduleSymbol = resolveExternalModuleName(node, node.moduleSpecifier!); if (moduleSymbol && hasExportAssignmentSymbol(moduleSymbol)) { error(node.moduleSpecifier, Diagnostics.Module_0_uses_export_and_cannot_be_used_with_export_Asterisk, symbolToString(moduleSymbol)); } else if (node.exportClause) { checkAliasSymbol(node.exportClause); } if (moduleKind !== ModuleKind.System && moduleKind < ModuleKind.ES2015) { if (node.exportClause) { // export * as ns from "foo"; // For ES2015 modules, we emit it as a pair of `import * as a_1 ...; export { a_1 as ns }` and don't need the helper. // We only use the helper here when in esModuleInterop if (compilerOptions.esModuleInterop) { checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); } } else { // export * from "foo" checkExternalEmitHelpers(node, ExternalEmitHelpers.ExportStar); } } } } } function checkGrammarExportDeclaration(node: ExportDeclaration): boolean { const isTypeOnlyExportStar = node.isTypeOnly && node.exportClause?.kind !== SyntaxKind.NamedExports; if (isTypeOnlyExportStar) { grammarErrorOnNode(node, Diagnostics.Only_named_exports_may_use_export_type); } return !isTypeOnlyExportStar; } function checkGrammarModuleElementContext(node: Statement, errorMessage: DiagnosticMessage): boolean { const isInAppropriateContext = node.parent.kind === SyntaxKind.SourceFile || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.ModuleDeclaration; if (!isInAppropriateContext) { grammarErrorOnFirstToken(node, errorMessage); } return !isInAppropriateContext; } function importClauseContainsReferencedImport(importClause: ImportClause) { return forEachImportClauseDeclaration(importClause, declaration => { return !!getSymbolOfNode(declaration).isReferenced; }); } function importClauseContainsConstEnumUsedAsValue(importClause: ImportClause) { return forEachImportClauseDeclaration(importClause, declaration => { return !!getSymbolLinks(getSymbolOfNode(declaration)).constEnumReferenced; }); } function checkImportsForTypeOnlyConversion(sourceFile: SourceFile) { for (const statement of sourceFile.statements) { if ( isImportDeclaration(statement) && statement.importClause && !statement.importClause.isTypeOnly && importClauseContainsReferencedImport(statement.importClause) && !isReferencedAliasDeclaration(statement.importClause, /*checkChildren*/ true) && !importClauseContainsConstEnumUsedAsValue(statement.importClause) ) { error( statement, Diagnostics.This_import_is_never_used_as_a_value_and_must_use_import_type_because_the_importsNotUsedAsValues_is_set_to_error); } } } function checkExportSpecifier(node: ExportSpecifier) { checkAliasSymbol(node); if (getEmitDeclarations(compilerOptions)) { collectLinkedAliases(node.propertyName || node.name, /*setVisibility*/ true); } if (!node.parent.parent.moduleSpecifier) { const exportedName = node.propertyName || node.name; // find immediate value referenced by exported name (SymbolFlags.Alias is set so we don't chase down aliases) const symbol = resolveName(exportedName, exportedName.escapedText, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); if (symbol && (symbol === undefinedSymbol || symbol === globalThisSymbol || isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName)); } else { markExportAsReferenced(node); const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol); if (!target || target === unknownSymbol || target.flags & SymbolFlags.Value) { checkExpressionCached(node.propertyName || node.name); } } } else { if (compilerOptions.esModuleInterop && moduleKind !== ModuleKind.System && moduleKind < ModuleKind.ES2015 && idText(node.propertyName || node.name) === "default") { checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); } } } function checkExportAssignment(node: ExportAssignment) { if (checkGrammarModuleElementContext(node, Diagnostics.An_export_assignment_can_only_be_used_in_a_module)) { // If we hit an export assignment in an illegal context, just bail out to avoid cascading errors. return; } const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : node.parent.parent; if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) { if (node.isExportEquals) { error(node, Diagnostics.An_export_assignment_cannot_be_used_in_a_namespace); } else { error(node, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module); } return; } // Grammar checking if (!checkGrammarDecoratorsAndModifiers(node) && hasEffectiveModifiers(node)) { grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers); } if (node.expression.kind === SyntaxKind.Identifier) { const id = node.expression as Identifier; const sym = resolveEntityName(id, SymbolFlags.All, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, node); if (sym) { markAliasReferenced(sym, id); // If not a value, we're interpreting the identifier as a type export, along the lines of (`export { Id as default }`) const target = sym.flags & SymbolFlags.Alias ? resolveAlias(sym) : sym; if (target === unknownSymbol || target.flags & SymbolFlags.Value) { // However if it is a value, we need to check it's being used correctly checkExpressionCached(node.expression); } } else { checkExpressionCached(node.expression); // doesn't resolve, check as expression to mark as error } if (getEmitDeclarations(compilerOptions)) { collectLinkedAliases(node.expression as Identifier, /*setVisibility*/ true); } } else { checkExpressionCached(node.expression); } checkExternalModuleExports(container); if ((node.flags & NodeFlags.Ambient) && !isEntityNameExpression(node.expression)) { grammarErrorOnNode(node.expression, Diagnostics.The_expression_of_an_export_assignment_must_be_an_identifier_or_qualified_name_in_an_ambient_context); } if (node.isExportEquals && !(node.flags & NodeFlags.Ambient)) { if (moduleKind >= ModuleKind.ES2015) { // export assignment is not supported in es6 modules grammarErrorOnNode(node, Diagnostics.Export_assignment_cannot_be_used_when_targeting_ECMAScript_modules_Consider_using_export_default_or_another_module_format_instead); } else if (moduleKind === ModuleKind.System) { // system modules does not support export assignment grammarErrorOnNode(node, Diagnostics.Export_assignment_is_not_supported_when_module_flag_is_system); } } } function hasExportedMembers(moduleSymbol: Symbol) { return forEachEntry(moduleSymbol.exports!, (_, id) => id !== "export="); } function checkExternalModuleExports(node: SourceFile | ModuleDeclaration) { const moduleSymbol = getSymbolOfNode(node); const links = getSymbolLinks(moduleSymbol); if (!links.exportsChecked) { const exportEqualsSymbol = moduleSymbol.exports!.get("export=" as __String); if (exportEqualsSymbol && hasExportedMembers(moduleSymbol)) { const declaration = getDeclarationOfAliasSymbol(exportEqualsSymbol) || exportEqualsSymbol.valueDeclaration; if (!isTopLevelInExternalModuleAugmentation(declaration) && !isInJSFile(declaration)) { error(declaration, Diagnostics.An_export_assignment_cannot_be_used_in_a_module_with_other_exported_elements); } } // Checks for export * conflicts const exports = getExportsOfModule(moduleSymbol); if (exports) { exports.forEach(({ declarations, flags }, id) => { if (id === "__export") { return; } // ECMA262: 15.2.1.1 It is a Syntax Error if the ExportedNames of ModuleItemList contains any duplicate entries. // (TS Exceptions: namespaces, function overloads, enums, and interfaces) if (flags & (SymbolFlags.Namespace | SymbolFlags.Interface | SymbolFlags.Enum)) { return; } const exportedDeclarationsCount = countWhere(declarations, isNotOverloadAndNotAccessor); if (flags & SymbolFlags.TypeAlias && exportedDeclarationsCount <= 2) { // it is legal to merge type alias with other values // so count should be either 1 (just type alias) or 2 (type alias + merged value) return; } if (exportedDeclarationsCount > 1) { for (const declaration of declarations) { if (isNotOverload(declaration)) { diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Cannot_redeclare_exported_variable_0, unescapeLeadingUnderscores(id))); } } } }); } links.exportsChecked = true; } } function checkSourceElement(node: Node | undefined): void { if (node) { const saveCurrentNode = currentNode; currentNode = node; instantiationCount = 0; checkSourceElementWorker(node); currentNode = saveCurrentNode; } } function checkSourceElementWorker(node: Node): void { if (isInJSFile(node)) { forEach((node as JSDocContainer).jsDoc, ({ tags }) => forEach(tags, checkSourceElement)); } const kind = node.kind; if (cancellationToken) { // Only bother checking on a few construct kinds. We don't want to be excessively // hitting the cancellation token on every node we check. switch (kind) { case SyntaxKind.ModuleDeclaration: case SyntaxKind.ClassDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.FunctionDeclaration: cancellationToken.throwIfCancellationRequested(); } } if (kind >= SyntaxKind.FirstStatement && kind <= SyntaxKind.LastStatement && node.flowNode && !isReachableFlowNode(node.flowNode)) { errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected); } switch (kind) { case SyntaxKind.TypeParameter: return checkTypeParameter(node); case SyntaxKind.Parameter: return checkParameter(node); case SyntaxKind.PropertyDeclaration: return checkPropertyDeclaration(node); case SyntaxKind.PropertySignature: return checkPropertySignature(node); case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.IndexSignature: return checkSignatureDeclaration(node); case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: return checkMethodDeclaration(node); case SyntaxKind.Constructor: return checkConstructorDeclaration(node); case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: return checkAccessorDeclaration(node); case SyntaxKind.TypeReference: return checkTypeReferenceNode(node); case SyntaxKind.TypePredicate: return checkTypePredicate(node); case SyntaxKind.TypeQuery: return checkTypeQuery(node); case SyntaxKind.TypeLiteral: return checkTypeLiteral(node); case SyntaxKind.ArrayType: return checkArrayType(node); case SyntaxKind.TupleType: return checkTupleType(node); case SyntaxKind.UnionType: case SyntaxKind.IntersectionType: return checkUnionOrIntersectionType(node); case SyntaxKind.ParenthesizedType: case SyntaxKind.OptionalType: case SyntaxKind.RestType: return checkSourceElement((node).type); case SyntaxKind.ThisType: return checkThisType(node); case SyntaxKind.TypeOperator: return checkTypeOperator(node); case SyntaxKind.ConditionalType: return checkConditionalType(node); case SyntaxKind.InferType: return checkInferType(node); case SyntaxKind.TemplateLiteralType: return checkTemplateLiteralType(node); case SyntaxKind.ImportType: return checkImportType(node); case SyntaxKind.NamedTupleMember: return checkNamedTupleMember(node); case SyntaxKind.JSDocAugmentsTag: return checkJSDocAugmentsTag(node as JSDocAugmentsTag); case SyntaxKind.JSDocImplementsTag: return checkJSDocImplementsTag(node as JSDocImplementsTag); case SyntaxKind.JSDocTypedefTag: case SyntaxKind.JSDocCallbackTag: case SyntaxKind.JSDocEnumTag: return checkJSDocTypeAliasTag(node as JSDocTypedefTag); case SyntaxKind.JSDocTemplateTag: return checkJSDocTemplateTag(node as JSDocTemplateTag); case SyntaxKind.JSDocTypeTag: return checkJSDocTypeTag(node as JSDocTypeTag); case SyntaxKind.JSDocParameterTag: return checkJSDocParameterTag(node as JSDocParameterTag); case SyntaxKind.JSDocPropertyTag: return checkJSDocPropertyTag(node as JSDocPropertyTag); case SyntaxKind.JSDocFunctionType: checkJSDocFunctionType(node as JSDocFunctionType); // falls through case SyntaxKind.JSDocNonNullableType: case SyntaxKind.JSDocNullableType: case SyntaxKind.JSDocAllType: case SyntaxKind.JSDocUnknownType: case SyntaxKind.JSDocTypeLiteral: checkJSDocTypeIsInJsFile(node); forEachChild(node, checkSourceElement); return; case SyntaxKind.JSDocVariadicType: checkJSDocVariadicType(node as JSDocVariadicType); return; case SyntaxKind.JSDocTypeExpression: return checkSourceElement((node as JSDocTypeExpression).type); case SyntaxKind.IndexedAccessType: return checkIndexedAccessType(node); case SyntaxKind.MappedType: return checkMappedType(node); case SyntaxKind.FunctionDeclaration: return checkFunctionDeclaration(node); case SyntaxKind.Block: case SyntaxKind.ModuleBlock: return checkBlock(node); case SyntaxKind.VariableStatement: return checkVariableStatement(node); case SyntaxKind.ExpressionStatement: return checkExpressionStatement(node); case SyntaxKind.IfStatement: return checkIfStatement(node); case SyntaxKind.DoStatement: return checkDoStatement(node); case SyntaxKind.WhileStatement: return checkWhileStatement(node); case SyntaxKind.ForStatement: return checkForStatement(node); case SyntaxKind.ForInStatement: return checkForInStatement(node); case SyntaxKind.ForOfStatement: return checkForOfStatement(node); case SyntaxKind.ContinueStatement: case SyntaxKind.BreakStatement: return checkBreakOrContinueStatement(node); case SyntaxKind.ReturnStatement: return checkReturnStatement(node); case SyntaxKind.WithStatement: return checkWithStatement(node); case SyntaxKind.SwitchStatement: return checkSwitchStatement(node); case SyntaxKind.LabeledStatement: return checkLabeledStatement(node); case SyntaxKind.ThrowStatement: return checkThrowStatement(node); case SyntaxKind.TryStatement: return checkTryStatement(node); case SyntaxKind.VariableDeclaration: return checkVariableDeclaration(node); case SyntaxKind.BindingElement: return checkBindingElement(node); case SyntaxKind.ClassDeclaration: return checkClassDeclaration(node); case SyntaxKind.InterfaceDeclaration: return checkInterfaceDeclaration(node); case SyntaxKind.TypeAliasDeclaration: return checkTypeAliasDeclaration(node); case SyntaxKind.EnumDeclaration: return checkEnumDeclaration(node); case SyntaxKind.ModuleDeclaration: return checkModuleDeclaration(node); case SyntaxKind.ImportDeclaration: return checkImportDeclaration(node); case SyntaxKind.ImportEqualsDeclaration: return checkImportEqualsDeclaration(node); case SyntaxKind.ExportDeclaration: return checkExportDeclaration(node); case SyntaxKind.ExportAssignment: return checkExportAssignment(node); case SyntaxKind.EmptyStatement: case SyntaxKind.DebuggerStatement: checkGrammarStatementInAmbientContext(node); return; case SyntaxKind.MissingDeclaration: return checkMissingDeclaration(node); } } function checkJSDocTypeIsInJsFile(node: Node): void { if (!isInJSFile(node)) { grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); } } function checkJSDocVariadicType(node: JSDocVariadicType): void { checkJSDocTypeIsInJsFile(node); checkSourceElement(node.type); // Only legal location is in the *last* parameter tag or last parameter of a JSDoc function. const { parent } = node; if (isParameter(parent) && isJSDocFunctionType(parent.parent)) { if (last(parent.parent.parameters) !== parent) { error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); } return; } if (!isJSDocTypeExpression(parent)) { error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); } const paramTag = node.parent.parent; if (!isJSDocParameterTag(paramTag)) { error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); return; } const param = getParameterSymbolFromJSDoc(paramTag); if (!param) { // We will error in `checkJSDocParameterTag`. return; } const host = getHostSignatureFromJSDoc(paramTag); if (!host || last(host.parameters).symbol !== param) { error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); } } function getTypeFromJSDocVariadicType(node: JSDocVariadicType): Type { const type = getTypeFromTypeNode(node.type); const { parent } = node; const paramTag = node.parent.parent; if (isJSDocTypeExpression(node.parent) && isJSDocParameterTag(paramTag)) { // Else we will add a diagnostic, see `checkJSDocVariadicType`. const host = getHostSignatureFromJSDoc(paramTag); if (host) { /* Only return an array type if the corresponding parameter is marked as a rest parameter, or if there are no parameters. So in the following situation we will not create an array type: /** @param {...number} a * / function f(a) {} Because `a` will just be of type `number | undefined`. A synthetic `...args` will also be added, which *will* get an array type. */ const lastParamDeclaration = lastOrUndefined(host.parameters); const symbol = getParameterSymbolFromJSDoc(paramTag); if (!lastParamDeclaration || symbol && lastParamDeclaration.symbol === symbol && isRestParameter(lastParamDeclaration)) { return createArrayType(type); } } } if (isParameter(parent) && isJSDocFunctionType(parent.parent)) { return createArrayType(type); } return addOptionality(type); } // Function and class expression bodies are checked after all statements in the enclosing body. This is // to ensure constructs like the following are permitted: // const foo = function () { // const s = foo(); // return "hello"; // } // Here, performing a full type check of the body of the function expression whilst in the process of // determining the type of foo would cause foo to be given type any because of the recursive reference. // Delaying the type check of the body ensures foo has been assigned a type. function checkNodeDeferred(node: Node) { const enclosingFile = getSourceFileOfNode(node); const links = getNodeLinks(enclosingFile); if (!(links.flags & NodeCheckFlags.TypeChecked)) { links.deferredNodes = links.deferredNodes || new Map(); const id = getNodeId(node); links.deferredNodes.set(id, node); } } function checkDeferredNodes(context: SourceFile) { const links = getNodeLinks(context); if (links.deferredNodes) { links.deferredNodes.forEach(checkDeferredNode); } } function checkDeferredNode(node: Node) { const saveCurrentNode = currentNode; currentNode = node; instantiationCount = 0; switch (node.kind) { case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: case SyntaxKind.TaggedTemplateExpression: case SyntaxKind.Decorator: case SyntaxKind.JsxOpeningElement: // These node kinds are deferred checked when overload resolution fails // To save on work, we ensure the arguments are checked just once, in // a deferred way resolveUntypedCall(node as CallLikeExpression); break; case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: checkFunctionExpressionOrObjectLiteralMethodDeferred(node); break; case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: checkAccessorDeclaration(node); break; case SyntaxKind.ClassExpression: checkClassExpressionDeferred(node); break; case SyntaxKind.JsxSelfClosingElement: checkJsxSelfClosingElementDeferred(node); break; case SyntaxKind.JsxElement: checkJsxElementDeferred(node); break; } currentNode = saveCurrentNode; } function checkSourceFile(node: SourceFile) { const tracingData: tracing.EventData = [tracing.Phase.Check, "checkSourceFile", { path: node.path }]; tracing.begin(...tracingData); performance.mark("beforeCheck"); checkSourceFileWorker(node); performance.mark("afterCheck"); performance.measure("Check", "beforeCheck", "afterCheck"); tracing.end(...tracingData); } function unusedIsError(kind: UnusedKind, isAmbient: boolean): boolean { if (isAmbient) { return false; } switch (kind) { case UnusedKind.Local: return !!compilerOptions.noUnusedLocals; case UnusedKind.Parameter: return !!compilerOptions.noUnusedParameters; default: return Debug.assertNever(kind); } } function getPotentiallyUnusedIdentifiers(sourceFile: SourceFile): readonly PotentiallyUnusedIdentifier[] { return allPotentiallyUnusedIdentifiers.get(sourceFile.path) || emptyArray; } // Fully type check a source file and collect the relevant diagnostics. function checkSourceFileWorker(node: SourceFile) { const links = getNodeLinks(node); if (!(links.flags & NodeCheckFlags.TypeChecked)) { if (skipTypeChecking(node, compilerOptions, host)) { return; } // Grammar checking checkGrammarSourceFile(node); clear(potentialThisCollisions); clear(potentialNewTargetCollisions); clear(potentialWeakMapCollisions); forEach(node.statements, checkSourceElement); checkSourceElement(node.endOfFileToken); checkDeferredNodes(node); if (isExternalOrCommonJsModule(node)) { registerForUnusedIdentifiersCheck(node); } if (!node.isDeclarationFile && (compilerOptions.noUnusedLocals || compilerOptions.noUnusedParameters)) { checkUnusedIdentifiers(getPotentiallyUnusedIdentifiers(node), (containingNode, kind, diag) => { if (!containsParseError(containingNode) && unusedIsError(kind, !!(containingNode.flags & NodeFlags.Ambient))) { diagnostics.add(diag); } }); } if (compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && !node.isDeclarationFile && isExternalModule(node) ) { checkImportsForTypeOnlyConversion(node); } if (isExternalOrCommonJsModule(node)) { checkExternalModuleExports(node); } if (potentialThisCollisions.length) { forEach(potentialThisCollisions, checkIfThisIsCapturedInEnclosingScope); clear(potentialThisCollisions); } if (potentialNewTargetCollisions.length) { forEach(potentialNewTargetCollisions, checkIfNewTargetIsCapturedInEnclosingScope); clear(potentialNewTargetCollisions); } if (potentialWeakMapCollisions.length) { forEach(potentialWeakMapCollisions, checkWeakMapCollision); clear(potentialWeakMapCollisions); } links.flags |= NodeCheckFlags.TypeChecked; } } function getDiagnostics(sourceFile: SourceFile, ct: CancellationToken): Diagnostic[] { 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; return getDiagnosticsWorker(sourceFile); } finally { cancellationToken = undefined; } } function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] { throwIfNonDiagnosticsProducing(); if (sourceFile) { // Some global diagnostics are deferred until they are needed and // may not be reported in the first call to getGlobalDiagnostics. // We should catch these changes and report them. const previousGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); const previousGlobalDiagnosticsSize = previousGlobalDiagnostics.length; checkSourceFile(sourceFile); const semanticDiagnostics = diagnostics.getDiagnostics(sourceFile.fileName); const currentGlobalDiagnostics = diagnostics.getGlobalDiagnostics(); if (currentGlobalDiagnostics !== previousGlobalDiagnostics) { // If the arrays are not the same reference, new diagnostics were added. const deferredGlobalDiagnostics = relativeComplement(previousGlobalDiagnostics, currentGlobalDiagnostics, compareDiagnostics); return concatenate(deferredGlobalDiagnostics, semanticDiagnostics); } else if (previousGlobalDiagnosticsSize === 0 && currentGlobalDiagnostics.length > 0) { // If the arrays are the same reference, but the length has changed, a single // new diagnostic was added as DiagnosticCollection attempts to reuse the // same array. return concatenate(currentGlobalDiagnostics, semanticDiagnostics); } return semanticDiagnostics; } // Global diagnostics are always added when a file is not provided to // getDiagnostics forEach(host.getSourceFiles(), checkSourceFile); return diagnostics.getDiagnostics(); } function getGlobalDiagnostics(): Diagnostic[] { throwIfNonDiagnosticsProducing(); return diagnostics.getGlobalDiagnostics(); } function throwIfNonDiagnosticsProducing() { if (!produceDiagnostics) { throw new Error("Trying to get diagnostics from a type checker that does not produce them."); } } // Language service support function getSymbolsInScope(location: Node, meaning: SymbolFlags): Symbol[] { if (location.flags & NodeFlags.InWithStatement) { // We cannot answer semantic questions within a with block, do not proceed any further return []; } const symbols = createSymbolTable(); let isStatic = false; populateSymbols(); symbols.delete(InternalSymbolName.This); // Not a symbol, a keyword return symbolsToArray(symbols); function populateSymbols() { while (location) { if (location.locals && !isGlobalSourceFile(location)) { copySymbols(location.locals, meaning); } switch (location.kind) { case SyntaxKind.SourceFile: if (!isExternalOrCommonJsModule(location)) break; // falls through case SyntaxKind.ModuleDeclaration: copySymbols(getSymbolOfNode(location as ModuleDeclaration | SourceFile).exports!, meaning & SymbolFlags.ModuleMember); break; case SyntaxKind.EnumDeclaration: copySymbols(getSymbolOfNode(location as EnumDeclaration).exports!, meaning & SymbolFlags.EnumMember); break; case SyntaxKind.ClassExpression: const className = (location as ClassExpression).name; if (className) { copySymbol(location.symbol, meaning); } // this fall-through is necessary because we would like to handle // type parameter inside class expression similar to how we handle it in classDeclaration and interface Declaration. // falls through case SyntaxKind.ClassDeclaration: case SyntaxKind.InterfaceDeclaration: // If we didn't come from static member of class or interface, // add the type parameters into the symbol table // (type parameters of classDeclaration/classExpression and interface are in member property of the symbol. // Note: that the memberFlags come from previous iteration. if (!isStatic) { copySymbols(getMembersOfSymbol(getSymbolOfNode(location as ClassDeclaration | InterfaceDeclaration)), meaning & SymbolFlags.Type); } break; case SyntaxKind.FunctionExpression: const funcName = (location as FunctionExpression).name; if (funcName) { copySymbol(location.symbol, meaning); } break; } if (introducesArgumentsExoticObject(location)) { copySymbol(argumentsSymbol, meaning); } isStatic = hasSyntacticModifier(location, ModifierFlags.Static); location = location.parent; } copySymbols(globals, meaning); } /** * Copy the given symbol into symbol tables if the symbol has the given meaning * and it doesn't already existed in the symbol table * @param key a key for storing in symbol table; if undefined, use symbol.name * @param symbol the symbol to be added into symbol table * @param meaning meaning of symbol to filter by before adding to symbol table */ function copySymbol(symbol: Symbol, meaning: SymbolFlags): void { if (getCombinedLocalAndExportSymbolFlags(symbol) & meaning) { const id = symbol.escapedName; // We will copy all symbol regardless of its reserved name because // symbolsToArray will check whether the key is a reserved name and // it will not copy symbol with reserved name to the array if (!symbols.has(id)) { symbols.set(id, symbol); } } } function copySymbols(source: SymbolTable, meaning: SymbolFlags): void { if (meaning) { source.forEach(symbol => { copySymbol(symbol, meaning); }); } } } function isTypeDeclarationName(name: Node): boolean { return name.kind === SyntaxKind.Identifier && isTypeDeclaration(name.parent) && getNameOfDeclaration(name.parent) === name; } function isTypeDeclaration(node: Node): node is TypeParameterDeclaration | ClassDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag | EnumDeclaration | ImportClause | ImportSpecifier | ExportSpecifier { switch (node.kind) { case SyntaxKind.TypeParameter: case SyntaxKind.ClassDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.TypeAliasDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.JSDocTypedefTag: case SyntaxKind.JSDocCallbackTag: case SyntaxKind.JSDocEnumTag: return true; case SyntaxKind.ImportClause: return (node as ImportClause).isTypeOnly; case SyntaxKind.ImportSpecifier: case SyntaxKind.ExportSpecifier: return (node as ImportSpecifier | ExportSpecifier).parent.parent.isTypeOnly; default: return false; } } // True if the given identifier is part of a type reference function isTypeReferenceIdentifier(node: EntityName): boolean { while (node.parent.kind === SyntaxKind.QualifiedName) { node = node.parent as QualifiedName; } return node.parent.kind === SyntaxKind.TypeReference; } function isHeritageClauseElementIdentifier(node: Node): boolean { while (node.parent.kind === SyntaxKind.PropertyAccessExpression) { node = node.parent; } return node.parent.kind === SyntaxKind.ExpressionWithTypeArguments; } function isJSDocEntryNameReference(node: Identifier | PrivateIdentifier | PropertyAccessExpression | QualifiedName): boolean { while (node.parent.kind === SyntaxKind.QualifiedName) { node = node.parent as QualifiedName; } while (node.parent.kind === SyntaxKind.PropertyAccessExpression) { node = node.parent as PropertyAccessExpression; } return node.parent.kind === SyntaxKind.JSDocNameReference; } function forEachEnclosingClass(node: Node, callback: (node: Node) => T | undefined): T | undefined { let result: T | undefined; while (true) { node = getContainingClass(node)!; if (!node) break; if (result = callback(node)) break; } return result; } function isNodeUsedDuringClassInitialization(node: Node) { return !!findAncestor(node, element => { if (isConstructorDeclaration(element) && nodeIsPresent(element.body) || isPropertyDeclaration(element)) { return true; } else if (isClassLike(element) || isFunctionLikeDeclaration(element)) { return "quit"; } return false; }); } function isNodeWithinClass(node: Node, classDeclaration: ClassLikeDeclaration) { return !!forEachEnclosingClass(node, n => n === classDeclaration); } function getLeftSideOfImportEqualsOrExportAssignment(nodeOnRightSide: EntityName): ImportEqualsDeclaration | ExportAssignment | undefined { while (nodeOnRightSide.parent.kind === SyntaxKind.QualifiedName) { nodeOnRightSide = nodeOnRightSide.parent; } if (nodeOnRightSide.parent.kind === SyntaxKind.ImportEqualsDeclaration) { return (nodeOnRightSide.parent).moduleReference === nodeOnRightSide ? nodeOnRightSide.parent : undefined; } if (nodeOnRightSide.parent.kind === SyntaxKind.ExportAssignment) { return (nodeOnRightSide.parent).expression === nodeOnRightSide ? nodeOnRightSide.parent : undefined; } return undefined; } function isInRightSideOfImportOrExportAssignment(node: EntityName) { return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined; } function getSpecialPropertyAssignmentSymbolFromEntityName(entityName: EntityName | PropertyAccessExpression) { const specialPropertyAssignmentKind = getAssignmentDeclarationKind(entityName.parent.parent as BinaryExpression); switch (specialPropertyAssignmentKind) { case AssignmentDeclarationKind.ExportsProperty: case AssignmentDeclarationKind.PrototypeProperty: return getSymbolOfNode(entityName.parent); case AssignmentDeclarationKind.ThisProperty: case AssignmentDeclarationKind.ModuleExports: case AssignmentDeclarationKind.Property: return getSymbolOfNode(entityName.parent.parent); } } function isImportTypeQualifierPart(node: EntityName): ImportTypeNode | undefined { let parent = node.parent; while (isQualifiedName(parent)) { node = parent; parent = parent.parent; } if (parent && parent.kind === SyntaxKind.ImportType && (parent as ImportTypeNode).qualifier === node) { return parent as ImportTypeNode; } return undefined; } function getSymbolOfNameOrPropertyAccessExpression(name: EntityName | PrivateIdentifier | PropertyAccessExpression): Symbol | undefined { if (isDeclarationName(name)) { return getSymbolOfNode(name.parent); } if (isInJSFile(name) && name.parent.kind === SyntaxKind.PropertyAccessExpression && name.parent === (name.parent.parent as BinaryExpression).left) { // Check if this is a special property assignment if (!isPrivateIdentifier(name)) { const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(name); if (specialPropertyAssignmentSymbol) { return specialPropertyAssignmentSymbol; } } } if (name.parent.kind === SyntaxKind.ExportAssignment && isEntityNameExpression(name)) { // Even an entity name expression that doesn't resolve as an entityname may still typecheck as a property access expression const success = resolveEntityName(name, /*all meanings*/ SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias, /*ignoreErrors*/ true); if (success && success !== unknownSymbol) { return success; } } else if (!isPropertyAccessExpression(name) && !isPrivateIdentifier(name) && isInRightSideOfImportOrExportAssignment(name)) { // Since we already checked for ExportAssignment, this really could only be an Import const importEqualsDeclaration = getAncestor(name, SyntaxKind.ImportEqualsDeclaration); Debug.assert(importEqualsDeclaration !== undefined); return getSymbolOfPartOfRightHandSideOfImportEquals(name, /*dontResolveAlias*/ true); } if (!isPropertyAccessExpression(name) && !isPrivateIdentifier(name)) { const possibleImportNode = isImportTypeQualifierPart(name); if (possibleImportNode) { getTypeFromTypeNode(possibleImportNode); const sym = getNodeLinks(name).resolvedSymbol; return sym === unknownSymbol ? undefined : sym; } } while (isRightSideOfQualifiedNameOrPropertyAccess(name)) { name = name.parent; } if (isHeritageClauseElementIdentifier(name)) { let meaning = SymbolFlags.None; // In an interface or class, we're definitely interested in a type. if (name.parent.kind === SyntaxKind.ExpressionWithTypeArguments) { meaning = SymbolFlags.Type; // In a class 'extends' clause we are also looking for a value. if (isExpressionWithTypeArgumentsInClassExtendsClause(name.parent)) { meaning |= SymbolFlags.Value; } } else { meaning = SymbolFlags.Namespace; } meaning |= SymbolFlags.Alias; const entityNameSymbol = isEntityNameExpression(name) ? resolveEntityName(name, meaning) : undefined; if (entityNameSymbol) { return entityNameSymbol; } } if (name.parent.kind === SyntaxKind.JSDocParameterTag) { return getParameterSymbolFromJSDoc(name.parent as JSDocParameterTag); } if (name.parent.kind === SyntaxKind.TypeParameter && name.parent.parent.kind === SyntaxKind.JSDocTemplateTag) { Debug.assert(!isInJSFile(name)); // Otherwise `isDeclarationName` would have been true. const typeParameter = getTypeParameterFromJsDoc(name.parent as TypeParameterDeclaration & { parent: JSDocTemplateTag }); return typeParameter && typeParameter.symbol; } if (isExpressionNode(name)) { if (nodeIsMissing(name)) { // Missing entity name. return undefined; } if (name.kind === SyntaxKind.Identifier) { if (isJSXTagName(name) && isJsxIntrinsicIdentifier(name)) { const symbol = getIntrinsicTagSymbol(name.parent); return symbol === unknownSymbol ? undefined : symbol; } return resolveEntityName(name, SymbolFlags.Value, /*ignoreErrors*/ false, /*dontResolveAlias*/ true); } else if (name.kind === SyntaxKind.PropertyAccessExpression || name.kind === SyntaxKind.QualifiedName) { const links = getNodeLinks(name); if (links.resolvedSymbol) { return links.resolvedSymbol; } if (name.kind === SyntaxKind.PropertyAccessExpression) { checkPropertyAccessExpression(name); } else { checkQualifiedName(name); } return links.resolvedSymbol; } } else if (isTypeReferenceIdentifier(name)) { const meaning = name.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace; return resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true); } else if (isJSDocEntryNameReference(name)) { const meaning = SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value; return resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name)); } if (name.parent.kind === SyntaxKind.TypePredicate) { return resolveEntityName(name, /*meaning*/ SymbolFlags.FunctionScopedVariable); } // Do we want to return undefined here? return undefined; } function getSymbolAtLocation(node: Node, ignoreErrors?: boolean): Symbol | undefined { if (node.kind === SyntaxKind.SourceFile) { return isExternalModule(node) ? getMergedSymbol(node.symbol) : undefined; } const { parent } = node; const grandParent = parent.parent; if (node.flags & NodeFlags.InWithStatement) { // We cannot answer semantic questions within a with block, do not proceed any further return undefined; } if (isDeclarationNameOrImportPropertyName(node)) { // This is a declaration, call getSymbolOfNode const parentSymbol = getSymbolOfNode(parent)!; return isImportOrExportSpecifier(node.parent) && node.parent.propertyName === node ? getImmediateAliasedSymbol(parentSymbol) : parentSymbol; } else if (isLiteralComputedPropertyDeclarationName(node)) { return getSymbolOfNode(parent.parent); } if (node.kind === SyntaxKind.Identifier) { if (isInRightSideOfImportOrExportAssignment(node)) { return getSymbolOfNameOrPropertyAccessExpression(node); } else if (parent.kind === SyntaxKind.BindingElement && grandParent.kind === SyntaxKind.ObjectBindingPattern && node === (parent).propertyName) { const typeOfPattern = getTypeOfNode(grandParent); const propertyDeclaration = getPropertyOfType(typeOfPattern, (node).escapedText); if (propertyDeclaration) { return propertyDeclaration; } } } switch (node.kind) { case SyntaxKind.Identifier: case SyntaxKind.PrivateIdentifier: case SyntaxKind.PropertyAccessExpression: case SyntaxKind.QualifiedName: return getSymbolOfNameOrPropertyAccessExpression(node); case SyntaxKind.ThisKeyword: const container = getThisContainer(node, /*includeArrowFunctions*/ false); if (isFunctionLike(container)) { const sig = getSignatureFromDeclaration(container); if (sig.thisParameter) { return sig.thisParameter; } } if (isInExpressionContext(node)) { return checkExpression(node as Expression).symbol; } // falls through case SyntaxKind.ThisType: return getTypeFromThisTypeNode(node as ThisExpression | ThisTypeNode).symbol; case SyntaxKind.SuperKeyword: return checkExpression(node as Expression).symbol; case SyntaxKind.ConstructorKeyword: // constructor keyword for an overload, should take us to the definition if it exist const constructorDeclaration = node.parent; if (constructorDeclaration && constructorDeclaration.kind === SyntaxKind.Constructor) { return (constructorDeclaration.parent).symbol; } return undefined; case SyntaxKind.StringLiteral: case SyntaxKind.NoSubstitutionTemplateLiteral: // 1). import x = require("./mo/*gotToDefinitionHere*/d") // 2). External module name in an import declaration // 3). Dynamic import call or require in javascript // 4). type A = import("./f/*gotToDefinitionHere*/oo") if ((isExternalModuleImportEqualsDeclaration(node.parent.parent) && getExternalModuleImportEqualsDeclarationExpression(node.parent.parent) === node) || ((node.parent.kind === SyntaxKind.ImportDeclaration || node.parent.kind === SyntaxKind.ExportDeclaration) && (node.parent).moduleSpecifier === node) || ((isInJSFile(node) && isRequireCall(node.parent, /*checkArgumentIsStringLiteralLike*/ false)) || isImportCall(node.parent)) || (isLiteralTypeNode(node.parent) && isLiteralImportTypeNode(node.parent.parent) && node.parent.parent.argument === node.parent) ) { return resolveExternalModuleName(node, node, ignoreErrors); } if (isCallExpression(parent) && isBindableObjectDefinePropertyCall(parent) && parent.arguments[1] === node) { return getSymbolOfNode(parent); } // falls through case SyntaxKind.NumericLiteral: // index access const objectType = isElementAccessExpression(parent) ? parent.argumentExpression === node ? getTypeOfExpression(parent.expression) : undefined : isLiteralTypeNode(parent) && isIndexedAccessTypeNode(grandParent) ? getTypeFromTypeNode(grandParent.objectType) : undefined; return objectType && getPropertyOfType(objectType, escapeLeadingUnderscores((node as StringLiteral | NumericLiteral).text)); case SyntaxKind.DefaultKeyword: case SyntaxKind.FunctionKeyword: case SyntaxKind.EqualsGreaterThanToken: case SyntaxKind.ClassKeyword: return getSymbolOfNode(node.parent); case SyntaxKind.ImportType: return isLiteralImportTypeNode(node) ? getSymbolAtLocation(node.argument.literal, ignoreErrors) : undefined; case SyntaxKind.ExportKeyword: return isExportAssignment(node.parent) ? Debug.checkDefined(node.parent.symbol) : undefined; default: return undefined; } } function getShorthandAssignmentValueSymbol(location: Node): Symbol | undefined { if (location && location.kind === SyntaxKind.ShorthandPropertyAssignment) { return resolveEntityName((location).name, SymbolFlags.Value | SymbolFlags.Alias); } return undefined; } /** Returns the target of an export specifier without following aliases */ function getExportSpecifierLocalTargetSymbol(node: ExportSpecifier | Identifier): Symbol | undefined { if (isExportSpecifier(node)) { return node.parent.parent.moduleSpecifier ? getExternalModuleMember(node.parent.parent, node) : resolveEntityName(node.propertyName || node.name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); } else { return resolveEntityName(node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); } } function getTypeOfNode(node: Node): Type { if (isSourceFile(node) && !isExternalModule(node)) { return errorType; } if (node.flags & NodeFlags.InWithStatement) { // We cannot answer semantic questions within a with block, do not proceed any further return errorType; } const classDecl = tryGetClassImplementingOrExtendingExpressionWithTypeArguments(node); const classType = classDecl && getDeclaredTypeOfClassOrInterface(getSymbolOfNode(classDecl.class)); if (isPartOfTypeNode(node)) { const typeFromTypeNode = getTypeFromTypeNode(node); return classType ? getTypeWithThisArgument(typeFromTypeNode, classType.thisType) : typeFromTypeNode; } if (isExpressionNode(node)) { return getRegularTypeOfExpression(node); } if (classType && !classDecl!.isImplements) { // A SyntaxKind.ExpressionWithTypeArguments is considered a type node, except when it occurs in the // extends clause of a class. We handle that case here. const baseType = firstOrUndefined(getBaseTypes(classType)); return baseType ? getTypeWithThisArgument(baseType, classType.thisType) : errorType; } if (isTypeDeclaration(node)) { // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration const symbol = getSymbolOfNode(node); return getDeclaredTypeOfSymbol(symbol); } if (isTypeDeclarationName(node)) { const symbol = getSymbolAtLocation(node); return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; } if (isDeclaration(node)) { // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration const symbol = getSymbolOfNode(node); return getTypeOfSymbol(symbol); } if (isDeclarationNameOrImportPropertyName(node)) { const symbol = getSymbolAtLocation(node); if (symbol) { return getTypeOfSymbol(symbol); } return errorType; } if (isBindingPattern(node)) { return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true) || errorType; } if (isInRightSideOfImportOrExportAssignment(node)) { const symbol = getSymbolAtLocation(node); if (symbol) { const declaredType = getDeclaredTypeOfSymbol(symbol); return declaredType !== errorType ? declaredType : getTypeOfSymbol(symbol); } } return errorType; } // Gets the type of object literal or array literal of destructuring assignment. // { a } from // for ( { a } of elems) { // } // [ a ] from // [a] = [ some array ...] function getTypeOfAssignmentPattern(expr: AssignmentPattern): Type | undefined { Debug.assert(expr.kind === SyntaxKind.ObjectLiteralExpression || expr.kind === SyntaxKind.ArrayLiteralExpression); // If this is from "for of" // for ( { a } of elems) { // } if (expr.parent.kind === SyntaxKind.ForOfStatement) { const iteratedType = checkRightHandSideOfForOf(expr.parent); return checkDestructuringAssignment(expr, iteratedType || errorType); } // If this is from "for" initializer // for ({a } = elems[0];.....) { } if (expr.parent.kind === SyntaxKind.BinaryExpression) { const iteratedType = getTypeOfExpression((expr.parent).right); return checkDestructuringAssignment(expr, iteratedType || errorType); } // If this is from nested object binding pattern // for ({ skills: { primary, secondary } } = multiRobot, i = 0; i < 1; i++) { if (expr.parent.kind === SyntaxKind.PropertyAssignment) { const node = cast(expr.parent.parent, isObjectLiteralExpression); const typeOfParentObjectLiteral = getTypeOfAssignmentPattern(node) || errorType; const propertyIndex = indexOfNode(node.properties, expr.parent); return checkObjectLiteralDestructuringPropertyAssignment(node, typeOfParentObjectLiteral, propertyIndex); } // Array literal assignment - array destructuring pattern const node = cast(expr.parent, isArrayLiteralExpression); // [{ property1: p1, property2 }] = elems; const typeOfArrayLiteral = getTypeOfAssignmentPattern(node) || errorType; const elementType = checkIteratedTypeOrElementType(IterationUse.Destructuring, typeOfArrayLiteral, undefinedType, expr.parent) || errorType; return checkArrayLiteralDestructuringElementAssignment(node, typeOfArrayLiteral, node.elements.indexOf(expr), elementType); } // Gets the property symbol corresponding to the property in destructuring assignment // 'property1' from // for ( { property1: a } of elems) { // } // 'property1' at location 'a' from: // [a] = [ property1, property2 ] function getPropertySymbolOfDestructuringAssignment(location: Identifier) { // Get the type of the object or array literal and then look for property of given name in the type const typeOfObjectLiteral = getTypeOfAssignmentPattern(cast(location.parent.parent, isAssignmentPattern)); return typeOfObjectLiteral && getPropertyOfType(typeOfObjectLiteral, location.escapedText); } function getRegularTypeOfExpression(expr: Expression): Type { if (isRightSideOfQualifiedNameOrPropertyAccess(expr)) { expr = expr.parent; } return getRegularTypeOfLiteralType(getTypeOfExpression(expr)); } /** * Gets either the static or instance type of a class element, based on * whether the element is declared as "static". */ function getParentTypeOfClassElement(node: ClassElement) { const classSymbol = getSymbolOfNode(node.parent)!; return hasSyntacticModifier(node, ModifierFlags.Static) ? getTypeOfSymbol(classSymbol) : getDeclaredTypeOfSymbol(classSymbol); } function getClassElementPropertyKeyType(element: ClassElement) { const name = element.name!; switch (name.kind) { case SyntaxKind.Identifier: return getLiteralType(idText(name)); case SyntaxKind.NumericLiteral: case SyntaxKind.StringLiteral: return getLiteralType(name.text); case SyntaxKind.ComputedPropertyName: const nameType = checkComputedPropertyName(name); return isTypeAssignableToKind(nameType, TypeFlags.ESSymbolLike) ? nameType : stringType; default: return Debug.fail("Unsupported property name."); } } // Return the list of properties of the given type, augmented with properties from Function // if the type has call or construct signatures function getAugmentedPropertiesOfType(type: Type): Symbol[] { type = getApparentType(type); const propsByName = createSymbolTable(getPropertiesOfType(type)); const functionType = getSignaturesOfType(type, SignatureKind.Call).length ? globalCallableFunctionType : getSignaturesOfType(type, SignatureKind.Construct).length ? globalNewableFunctionType : undefined; if (functionType) { forEach(getPropertiesOfType(functionType), p => { if (!propsByName.has(p.escapedName)) { propsByName.set(p.escapedName, p); } }); } return getNamedMembers(propsByName); } function typeHasCallOrConstructSignatures(type: Type): boolean { return ts.typeHasCallOrConstructSignatures(type, checker); } function getRootSymbols(symbol: Symbol): readonly Symbol[] { const roots = getImmediateRootSymbols(symbol); return roots ? flatMap(roots, getRootSymbols) : [symbol]; } function getImmediateRootSymbols(symbol: Symbol): readonly Symbol[] | undefined { if (getCheckFlags(symbol) & CheckFlags.Synthetic) { return mapDefined(getSymbolLinks(symbol).containingType!.types, type => getPropertyOfType(type, symbol.escapedName)); } else if (symbol.flags & SymbolFlags.Transient) { const { leftSpread, rightSpread, syntheticOrigin } = symbol as TransientSymbol; return leftSpread ? [leftSpread, rightSpread!] : syntheticOrigin ? [syntheticOrigin] : singleElementArray(tryGetAliasTarget(symbol)); } return undefined; } function tryGetAliasTarget(symbol: Symbol): Symbol | undefined { let target: Symbol | undefined; let next: Symbol | undefined = symbol; while (next = getSymbolLinks(next).target) { target = next; } return target; } // Emitter support function isArgumentsLocalBinding(nodeIn: Identifier): boolean { // Note: does not handle isShorthandPropertyAssignment (and probably a few more) if (isGeneratedIdentifier(nodeIn)) return false; const node = getParseTreeNode(nodeIn, isIdentifier); if (!node) return false; const parent = node.parent; if (!parent) return false; const isPropertyName = ((isPropertyAccessExpression(parent) || isPropertyAssignment(parent)) && parent.name === node); return !isPropertyName && getReferencedValueSymbol(node) === argumentsSymbol; } function moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean { let moduleSymbol = resolveExternalModuleName(moduleReferenceExpression.parent, moduleReferenceExpression); if (!moduleSymbol || isShorthandAmbientModuleSymbol(moduleSymbol)) { // If the module is not found or is shorthand, assume that it may export a value. return true; } const hasExportAssignment = hasExportAssignmentSymbol(moduleSymbol); // if module has export assignment then 'resolveExternalModuleSymbol' will return resolved symbol for export assignment // otherwise it will return moduleSymbol itself moduleSymbol = resolveExternalModuleSymbol(moduleSymbol); const symbolLinks = getSymbolLinks(moduleSymbol); if (symbolLinks.exportsSomeValue === undefined) { // for export assignments - check if resolved symbol for RHS is itself a value // otherwise - check if at least one export is value symbolLinks.exportsSomeValue = hasExportAssignment ? !!(moduleSymbol.flags & SymbolFlags.Value) : forEachEntry(getExportsOfModule(moduleSymbol), isValue); } return symbolLinks.exportsSomeValue!; function isValue(s: Symbol): boolean { s = resolveSymbol(s); return s && !!(s.flags & SymbolFlags.Value); } } function isNameOfModuleOrEnumDeclaration(node: Identifier) { return isModuleOrEnumDeclaration(node.parent) && node === node.parent.name; } // When resolved as an expression identifier, if the given node references an exported entity, return the declaration // node of the exported entity's container. Otherwise, return undefined. function getReferencedExportContainer(nodeIn: Identifier, prefixLocals?: boolean): SourceFile | ModuleDeclaration | EnumDeclaration | undefined { const node = getParseTreeNode(nodeIn, isIdentifier); if (node) { // When resolving the export container for the name of a module or enum // declaration, we need to start resolution at the declaration's container. // Otherwise, we could incorrectly resolve the export container as the // declaration if it contains an exported member with the same name. let symbol = getReferencedValueSymbol(node, /*startInDeclarationContainer*/ isNameOfModuleOrEnumDeclaration(node)); if (symbol) { if (symbol.flags & SymbolFlags.ExportValue) { // If we reference an exported entity within the same module declaration, then whether // we prefix depends on the kind of entity. SymbolFlags.ExportHasLocal encompasses all the // kinds that we do NOT prefix. const exportSymbol = getMergedSymbol(symbol.exportSymbol!); if (!prefixLocals && exportSymbol.flags & SymbolFlags.ExportHasLocal && !(exportSymbol.flags & SymbolFlags.Variable)) { return undefined; } symbol = exportSymbol; } const parentSymbol = getParentOfSymbol(symbol); if (parentSymbol) { if (parentSymbol.flags & SymbolFlags.ValueModule && parentSymbol.valueDeclaration.kind === SyntaxKind.SourceFile) { const symbolFile = parentSymbol.valueDeclaration; const referenceFile = getSourceFileOfNode(node); // If `node` accesses an export and that export isn't in the same file, then symbol is a namespace export, so return undefined. const symbolIsUmdExport = symbolFile !== referenceFile; return symbolIsUmdExport ? undefined : symbolFile; } return findAncestor(node.parent, (n): n is ModuleDeclaration | EnumDeclaration => isModuleOrEnumDeclaration(n) && getSymbolOfNode(n) === parentSymbol); } } } } // When resolved as an expression identifier, if the given node references an import, return the declaration of // that import. Otherwise, return undefined. function getReferencedImportDeclaration(nodeIn: Identifier): Declaration | undefined { if (nodeIn.generatedImportReference) { return nodeIn.generatedImportReference; } const node = getParseTreeNode(nodeIn, isIdentifier); if (node) { const symbol = getReferencedValueSymbol(node); // We should only get the declaration of an alias if there isn't a local value // declaration for the symbol if (isNonLocalAlias(symbol, /*excludes*/ SymbolFlags.Value) && !getTypeOnlyAliasDeclaration(symbol)) { return getDeclarationOfAliasSymbol(symbol); } } return undefined; } function isSymbolOfDestructuredElementOfCatchBinding(symbol: Symbol) { return isBindingElement(symbol.valueDeclaration) && walkUpBindingElementsAndPatterns(symbol.valueDeclaration).parent.kind === SyntaxKind.CatchClause; } function isSymbolOfDeclarationWithCollidingName(symbol: Symbol): boolean { if (symbol.flags & SymbolFlags.BlockScoped && !isSourceFile(symbol.valueDeclaration)) { const links = getSymbolLinks(symbol); if (links.isDeclarationWithCollidingName === undefined) { const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); if (isStatementWithLocals(container) || isSymbolOfDestructuredElementOfCatchBinding(symbol)) { const nodeLinks = getNodeLinks(symbol.valueDeclaration); if (resolveName(container.parent, symbol.escapedName, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false)) { // redeclaration - always should be renamed links.isDeclarationWithCollidingName = true; } else if (nodeLinks.flags & NodeCheckFlags.CapturedBlockScopedBinding) { // binding is captured in the function // should be renamed if: // - binding is not top level - top level bindings never collide with anything // AND // - binding is not declared in loop, should be renamed to avoid name reuse across siblings // let a, b // { let x = 1; a = () => x; } // { let x = 100; b = () => x; } // console.log(a()); // should print '1' // console.log(b()); // should print '100' // OR // - binding is declared inside loop but not in inside initializer of iteration statement or directly inside loop body // * variables from initializer are passed to rewritten loop body as parameters so they are not captured directly // * variables that are declared immediately in loop body will become top level variable after loop is rewritten and thus // they will not collide with anything const isDeclaredInLoop = nodeLinks.flags & NodeCheckFlags.BlockScopedBindingInLoop; const inLoopInitializer = isIterationStatement(container, /*lookInLabeledStatements*/ false); const inLoopBodyBlock = container.kind === SyntaxKind.Block && isIterationStatement(container.parent, /*lookInLabeledStatements*/ false); links.isDeclarationWithCollidingName = !isBlockScopedContainerTopLevel(container) && (!isDeclaredInLoop || (!inLoopInitializer && !inLoopBodyBlock)); } else { links.isDeclarationWithCollidingName = false; } } } return links.isDeclarationWithCollidingName!; } return false; } // When resolved as an expression identifier, if the given node references a nested block scoped entity with // a name that either hides an existing name or might hide it when compiled downlevel, // return the declaration of that entity. Otherwise, return undefined. function getReferencedDeclarationWithCollidingName(nodeIn: Identifier): Declaration | undefined { if (!isGeneratedIdentifier(nodeIn)) { const node = getParseTreeNode(nodeIn, isIdentifier); if (node) { const symbol = getReferencedValueSymbol(node); if (symbol && isSymbolOfDeclarationWithCollidingName(symbol)) { return symbol.valueDeclaration; } } } return undefined; } // Return true if the given node is a declaration of a nested block scoped entity with a name that either hides an // existing name or might hide a name when compiled downlevel function isDeclarationWithCollidingName(nodeIn: Declaration): boolean { const node = getParseTreeNode(nodeIn, isDeclaration); if (node) { const symbol = getSymbolOfNode(node); if (symbol) { return isSymbolOfDeclarationWithCollidingName(symbol); } } return false; } function isValueAliasDeclaration(node: Node): boolean { switch (node.kind) { case SyntaxKind.ImportEqualsDeclaration: return isAliasResolvedToValue(getSymbolOfNode(node) || unknownSymbol); case SyntaxKind.ImportClause: case SyntaxKind.NamespaceImport: case SyntaxKind.ImportSpecifier: case SyntaxKind.ExportSpecifier: const symbol = getSymbolOfNode(node) || unknownSymbol; return isAliasResolvedToValue(symbol) && !getTypeOnlyAliasDeclaration(symbol); case SyntaxKind.ExportDeclaration: const exportClause = (node).exportClause; return !!exportClause && ( isNamespaceExport(exportClause) || some(exportClause.elements, isValueAliasDeclaration) ); case SyntaxKind.ExportAssignment: return (node).expression && (node).expression.kind === SyntaxKind.Identifier ? isAliasResolvedToValue(getSymbolOfNode(node) || unknownSymbol) : true; } return false; } function isTopLevelValueImportEqualsWithEntityName(nodeIn: ImportEqualsDeclaration): boolean { const node = getParseTreeNode(nodeIn, isImportEqualsDeclaration); if (node === undefined || node.parent.kind !== SyntaxKind.SourceFile || !isInternalModuleImportEqualsDeclaration(node)) { // parent is not source file or it is not reference to internal module return false; } const isValue = isAliasResolvedToValue(getSymbolOfNode(node)); return isValue && node.moduleReference && !nodeIsMissing(node.moduleReference); } function isAliasResolvedToValue(symbol: Symbol): boolean { const target = resolveAlias(symbol); if (target === unknownSymbol) { return true; } // const enums and modules that contain only const enums are not considered values from the emit perspective // unless 'preserveConstEnums' option is set to true return !!(target.flags & SymbolFlags.Value) && (compilerOptions.preserveConstEnums || !isConstEnumOrConstEnumOnlyModule(target)); } function isConstEnumOrConstEnumOnlyModule(s: Symbol): boolean { return isConstEnumSymbol(s) || !!s.constEnumOnlyModule; } function isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean { if (isAliasSymbolDeclaration(node)) { const symbol = getSymbolOfNode(node); if (symbol && getSymbolLinks(symbol).referenced) { return true; } const target = getSymbolLinks(symbol!).target; // TODO: GH#18217 if (target && getEffectiveModifierFlags(node) & ModifierFlags.Export && target.flags & SymbolFlags.Value && (compilerOptions.preserveConstEnums || !isConstEnumOrConstEnumOnlyModule(target))) { // An `export import ... =` of a value symbol is always considered referenced return true; } } if (checkChildren) { return !!forEachChild(node, node => isReferencedAliasDeclaration(node, checkChildren)); } return false; } function isImplementationOfOverload(node: SignatureDeclaration) { if (nodeIsPresent((node as FunctionLikeDeclaration).body)) { if (isGetAccessor(node) || isSetAccessor(node)) return false; // Get or set accessors can never be overload implementations, but can have up to 2 signatures const symbol = getSymbolOfNode(node); const signaturesOfSymbol = getSignaturesOfSymbol(symbol); // If this function body corresponds to function with multiple signature, it is implementation of overload // e.g.: function foo(a: string): string; // function foo(a: number): number; // function foo(a: any) { // This is implementation of the overloads // return a; // } return signaturesOfSymbol.length > 1 || // If there is single signature for the symbol, it is overload if that signature isn't coming from the node // e.g.: function foo(a: string): string; // function foo(a: any) { // This is implementation of the overloads // return a; // } (signaturesOfSymbol.length === 1 && signaturesOfSymbol[0].declaration !== node); } return false; } function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag): boolean { return !!strictNullChecks && !isOptionalParameter(parameter) && !isJSDocParameterTag(parameter) && !!parameter.initializer && !hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); } function isOptionalUninitializedParameterProperty(parameter: ParameterDeclaration) { return strictNullChecks && isOptionalParameter(parameter) && !parameter.initializer && hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); } function isOptionalUninitializedParameter(parameter: ParameterDeclaration) { return !!strictNullChecks && isOptionalParameter(parameter) && !parameter.initializer; } function isExpandoFunctionDeclaration(node: Declaration): boolean { const declaration = getParseTreeNode(node, isFunctionDeclaration); if (!declaration) { return false; } const symbol = getSymbolOfNode(declaration); if (!symbol || !(symbol.flags & SymbolFlags.Function)) { return false; } return !!forEachEntry(getExportsOfSymbol(symbol), p => p.flags & SymbolFlags.Value && p.valueDeclaration && isPropertyAccessExpression(p.valueDeclaration)); } function getPropertiesOfContainerFunction(node: Declaration): Symbol[] { const declaration = getParseTreeNode(node, isFunctionDeclaration); if (!declaration) { return emptyArray; } const symbol = getSymbolOfNode(declaration); return symbol && getPropertiesOfType(getTypeOfSymbol(symbol)) || emptyArray; } function getNodeCheckFlags(node: Node): NodeCheckFlags { return getNodeLinks(node).flags || 0; } function getEnumMemberValue(node: EnumMember): string | number | undefined { computeEnumMemberValues(node.parent); return getNodeLinks(node).enumMemberValue; } function canHaveConstantValue(node: Node): node is EnumMember | AccessExpression { switch (node.kind) { case SyntaxKind.EnumMember: case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: return true; } return false; } function getConstantValue(node: EnumMember | AccessExpression): string | number | undefined { if (node.kind === SyntaxKind.EnumMember) { return getEnumMemberValue(node); } const symbol = getNodeLinks(node).resolvedSymbol; if (symbol && (symbol.flags & SymbolFlags.EnumMember)) { // inline property\index accesses only for const enums const member = symbol.valueDeclaration as EnumMember; if (isEnumConst(member.parent)) { return getEnumMemberValue(member); } } return undefined; } function isFunctionType(type: Type): boolean { return !!(type.flags & TypeFlags.Object) && getSignaturesOfType(type, SignatureKind.Call).length > 0; } function getTypeReferenceSerializationKind(typeNameIn: EntityName, location?: Node): TypeReferenceSerializationKind { // ensure both `typeName` and `location` are parse tree nodes. const typeName = getParseTreeNode(typeNameIn, isEntityName); if (!typeName) return TypeReferenceSerializationKind.Unknown; if (location) { location = getParseTreeNode(location); if (!location) return TypeReferenceSerializationKind.Unknown; } // Resolve the symbol as a value to ensure the type can be reached at runtime during emit. const valueSymbol = resolveEntityName(typeName, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location); const isTypeOnly = valueSymbol?.declarations?.every(isTypeOnlyImportOrExportDeclaration) || false; const resolvedSymbol = valueSymbol && valueSymbol.flags & SymbolFlags.Alias ? resolveAlias(valueSymbol) : valueSymbol; // Resolve the symbol as a type so that we can provide a more useful hint for the type serializer. const typeSymbol = resolveEntityName(typeName, SymbolFlags.Type, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, location); if (resolvedSymbol && resolvedSymbol === typeSymbol) { const globalPromiseSymbol = getGlobalPromiseConstructorSymbol(/*reportErrors*/ false); if (globalPromiseSymbol && resolvedSymbol === globalPromiseSymbol) { return TypeReferenceSerializationKind.Promise; } const constructorType = getTypeOfSymbol(resolvedSymbol); if (constructorType && isConstructorType(constructorType)) { return isTypeOnly ? TypeReferenceSerializationKind.TypeWithCallSignature : TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue; } } // We might not be able to resolve type symbol so use unknown type in that case (eg error case) if (!typeSymbol) { return isTypeOnly ? TypeReferenceSerializationKind.ObjectType : TypeReferenceSerializationKind.Unknown; } const type = getDeclaredTypeOfSymbol(typeSymbol); if (type === errorType) { return isTypeOnly ? TypeReferenceSerializationKind.ObjectType : TypeReferenceSerializationKind.Unknown; } else if (type.flags & TypeFlags.AnyOrUnknown) { return TypeReferenceSerializationKind.ObjectType; } else if (isTypeAssignableToKind(type, TypeFlags.Void | TypeFlags.Nullable | TypeFlags.Never)) { return TypeReferenceSerializationKind.VoidNullableOrNeverType; } else if (isTypeAssignableToKind(type, TypeFlags.BooleanLike)) { return TypeReferenceSerializationKind.BooleanType; } else if (isTypeAssignableToKind(type, TypeFlags.NumberLike)) { return TypeReferenceSerializationKind.NumberLikeType; } else if (isTypeAssignableToKind(type, TypeFlags.BigIntLike)) { return TypeReferenceSerializationKind.BigIntLikeType; } else if (isTypeAssignableToKind(type, TypeFlags.StringLike)) { return TypeReferenceSerializationKind.StringLikeType; } else if (isTupleType(type)) { return TypeReferenceSerializationKind.ArrayLikeType; } else if (isTypeAssignableToKind(type, TypeFlags.ESSymbolLike)) { return TypeReferenceSerializationKind.ESSymbolType; } else if (isFunctionType(type)) { return TypeReferenceSerializationKind.TypeWithCallSignature; } else if (isArrayType(type)) { return TypeReferenceSerializationKind.ArrayLikeType; } else { return TypeReferenceSerializationKind.ObjectType; } } function createTypeOfDeclaration(declarationIn: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean) { const declaration = getParseTreeNode(declarationIn, isVariableLikeOrAccessor); if (!declaration) { return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; } // Get type of the symbol if this is the valid symbol otherwise get type at location const symbol = getSymbolOfNode(declaration); let type = symbol && !(symbol.flags & (SymbolFlags.TypeLiteral | SymbolFlags.Signature)) ? getWidenedLiteralType(getTypeOfSymbol(symbol)) : errorType; if (type.flags & TypeFlags.UniqueESSymbol && type.symbol === symbol) { flags |= NodeBuilderFlags.AllowUniqueESSymbolType; } if (addUndefined) { type = getOptionalType(type); } return nodeBuilder.typeToTypeNode(type, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); } function createReturnTypeOfSignatureDeclaration(signatureDeclarationIn: SignatureDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { const signatureDeclaration = getParseTreeNode(signatureDeclarationIn, isFunctionLike); if (!signatureDeclaration) { return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; } const signature = getSignatureFromDeclaration(signatureDeclaration); return nodeBuilder.typeToTypeNode(getReturnTypeOfSignature(signature), enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); } function createTypeOfExpression(exprIn: Expression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker) { const expr = getParseTreeNode(exprIn, isExpression); if (!expr) { return factory.createToken(SyntaxKind.AnyKeyword) as KeywordTypeNode; } const type = getWidenedType(getRegularTypeOfExpression(expr)); return nodeBuilder.typeToTypeNode(type, enclosingDeclaration, flags | NodeBuilderFlags.MultilineObjectLiterals, tracker); } function hasGlobalName(name: string): boolean { return globals.has(escapeLeadingUnderscores(name)); } function getReferencedValueSymbol(reference: Identifier, startInDeclarationContainer?: boolean): Symbol | undefined { const resolvedSymbol = getNodeLinks(reference).resolvedSymbol; if (resolvedSymbol) { return resolvedSymbol; } let location: Node = reference; if (startInDeclarationContainer) { // When resolving the name of a declaration as a value, we need to start resolution // at a point outside of the declaration. const parent = reference.parent; if (isDeclaration(parent) && reference === parent.name) { location = getDeclarationContainer(parent); } } return resolveName(location, reference.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); } function getReferencedValueDeclaration(referenceIn: Identifier): Declaration | undefined { if (!isGeneratedIdentifier(referenceIn)) { const reference = getParseTreeNode(referenceIn, isIdentifier); if (reference) { const symbol = getReferencedValueSymbol(reference); if (symbol) { return getExportSymbolOfValueSymbolIfExported(symbol).valueDeclaration; } } } return undefined; } function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean { if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) { return isFreshLiteralType(getTypeOfSymbol(getSymbolOfNode(node))); } return false; } function literalTypeToNode(type: FreshableType, enclosing: Node, tracker: SymbolTracker): Expression { const enumResult = type.flags & TypeFlags.EnumLiteral ? nodeBuilder.symbolToExpression(type.symbol, SymbolFlags.Value, enclosing, /*flags*/ undefined, tracker) : type === trueType ? factory.createTrue() : type === falseType && factory.createFalse(); if (enumResult) return enumResult; const literalValue = (type as LiteralType).value; return typeof literalValue === "object" ? factory.createBigIntLiteral(literalValue) : typeof literalValue === "number" ? factory.createNumericLiteral(literalValue) : factory.createStringLiteral(literalValue); } function createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, tracker: SymbolTracker) { const type = getTypeOfSymbol(getSymbolOfNode(node)); return literalTypeToNode(type, node, tracker); } function getJsxFactoryEntity(location: Node): EntityName | undefined { return location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity; } function getJsxFragmentFactoryEntity(location: Node): EntityName | undefined { if (location) { const file = getSourceFileOfNode(location); if (file) { if (file.localJsxFragmentFactory) { return file.localJsxFragmentFactory; } const jsxFragPragmas = file.pragmas.get("jsxfrag"); const jsxFragPragma = isArray(jsxFragPragmas) ? jsxFragPragmas[0] : jsxFragPragmas; if (jsxFragPragma) { file.localJsxFragmentFactory = parseIsolatedEntityName(jsxFragPragma.arguments.factory, languageVersion); return file.localJsxFragmentFactory; } } } if (compilerOptions.jsxFragmentFactory) { return parseIsolatedEntityName(compilerOptions.jsxFragmentFactory, languageVersion); } } function createResolver(): EmitResolver { // this variable and functions that use it are deliberately moved here from the outer scope // to avoid scope pollution const resolvedTypeReferenceDirectives = host.getResolvedTypeReferenceDirectives(); let fileToDirective: ESMap; if (resolvedTypeReferenceDirectives) { // populate reverse mapping: file path -> type reference directive that was resolved to this file fileToDirective = new Map(); resolvedTypeReferenceDirectives.forEach((resolvedDirective, key) => { if (!resolvedDirective || !resolvedDirective.resolvedFileName) { return; } const file = host.getSourceFile(resolvedDirective.resolvedFileName); if (file) { // Add the transitive closure of path references loaded by this file (as long as they are not) // part of an existing type reference. addReferencedFilesToTypeDirective(file, key); } }); } return { getReferencedExportContainer, getReferencedImportDeclaration, getReferencedDeclarationWithCollidingName, isDeclarationWithCollidingName, isValueAliasDeclaration: nodeIn => { const node = getParseTreeNode(nodeIn); // Synthesized nodes are always treated like values. return node ? isValueAliasDeclaration(node) : true; }, hasGlobalName, isReferencedAliasDeclaration: (nodeIn, checkChildren?) => { const node = getParseTreeNode(nodeIn); // Synthesized nodes are always treated as referenced. return node ? isReferencedAliasDeclaration(node, checkChildren) : true; }, getNodeCheckFlags: nodeIn => { const node = getParseTreeNode(nodeIn); return node ? getNodeCheckFlags(node) : 0; }, isTopLevelValueImportEqualsWithEntityName, isDeclarationVisible, isImplementationOfOverload, isRequiredInitializedParameter, isOptionalUninitializedParameterProperty, isExpandoFunctionDeclaration, getPropertiesOfContainerFunction, createTypeOfDeclaration, createReturnTypeOfSignatureDeclaration, createTypeOfExpression, createLiteralConstValue, isSymbolAccessible, isEntityNameVisible, getConstantValue: nodeIn => { const node = getParseTreeNode(nodeIn, canHaveConstantValue); return node ? getConstantValue(node) : undefined; }, collectLinkedAliases, getReferencedValueDeclaration, getTypeReferenceSerializationKind, isOptionalParameter, moduleExportsSomeValue, isArgumentsLocalBinding, getExternalModuleFileFromDeclaration, getTypeReferenceDirectivesForEntityName, getTypeReferenceDirectivesForSymbol, isLiteralConstDeclaration, isLateBound: (nodeIn: Declaration): nodeIn is LateBoundDeclaration => { const node = getParseTreeNode(nodeIn, isDeclaration); const symbol = node && getSymbolOfNode(node); return !!(symbol && getCheckFlags(symbol) & CheckFlags.Late); }, getJsxFactoryEntity, getJsxFragmentFactoryEntity, getAllAccessorDeclarations(accessor: AccessorDeclaration): AllAccessorDeclarations { accessor = getParseTreeNode(accessor, isGetOrSetAccessorDeclaration)!; // TODO: GH#18217 const otherKind = accessor.kind === SyntaxKind.SetAccessor ? SyntaxKind.GetAccessor : SyntaxKind.SetAccessor; const otherAccessor = getDeclarationOfKind(getSymbolOfNode(accessor), otherKind); const firstAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? otherAccessor : accessor; const secondAccessor = otherAccessor && (otherAccessor.pos < accessor.pos) ? accessor : otherAccessor; const setAccessor = accessor.kind === SyntaxKind.SetAccessor ? accessor : otherAccessor as SetAccessorDeclaration; const getAccessor = accessor.kind === SyntaxKind.GetAccessor ? accessor : otherAccessor as GetAccessorDeclaration; return { firstAccessor, secondAccessor, setAccessor, getAccessor }; }, getSymbolOfExternalModuleSpecifier: moduleName => resolveExternalModuleNameWorker(moduleName, moduleName, /*moduleNotFoundError*/ undefined), isBindingCapturedByNode: (node, decl) => { const parseNode = getParseTreeNode(node); const parseDecl = getParseTreeNode(decl); return !!parseNode && !!parseDecl && (isVariableDeclaration(parseDecl) || isBindingElement(parseDecl)) && isBindingCapturedByNode(parseNode, parseDecl); }, getDeclarationStatementsForSourceFile: (node, flags, tracker, bundled) => { const n = getParseTreeNode(node) as SourceFile; Debug.assert(n && n.kind === SyntaxKind.SourceFile, "Non-sourcefile node passed into getDeclarationsForSourceFile"); const sym = getSymbolOfNode(node); if (!sym) { return !node.locals ? [] : nodeBuilder.symbolTableToDeclarationStatements(node.locals, node, flags, tracker, bundled); } return !sym.exports ? [] : nodeBuilder.symbolTableToDeclarationStatements(sym.exports, node, flags, tracker, bundled); }, isImportRequiredByAugmentation, }; function isImportRequiredByAugmentation(node: ImportDeclaration) { const file = getSourceFileOfNode(node); if (!file.symbol) return false; const importTarget = getExternalModuleFileFromDeclaration(node); if (!importTarget) return false; if (importTarget === file) return false; const exports = getExportsOfModule(file.symbol); for (const s of arrayFrom(exports.values())) { if (s.mergeId) { const merged = getMergedSymbol(s); for (const d of merged.declarations) { const declFile = getSourceFileOfNode(d); if (declFile === importTarget) { return true; } } } } return false; } function isInHeritageClause(node: PropertyAccessEntityNameExpression) { return node.parent && node.parent.kind === SyntaxKind.ExpressionWithTypeArguments && node.parent.parent && node.parent.parent.kind === SyntaxKind.HeritageClause; } // defined here to avoid outer scope pollution function getTypeReferenceDirectivesForEntityName(node: EntityNameOrEntityNameExpression): string[] | undefined { // program does not have any files with type reference directives - bail out if (!fileToDirective) { return undefined; } // property access can only be used as values, or types when within an expression with type arguments inside a heritage clause // qualified names can only be used as types\namespaces // identifiers are treated as values only if they appear in type queries let meaning = SymbolFlags.Type | SymbolFlags.Namespace; if ((node.kind === SyntaxKind.Identifier && isInTypeQuery(node)) || (node.kind === SyntaxKind.PropertyAccessExpression && !isInHeritageClause(node))) { meaning = SymbolFlags.Value | SymbolFlags.ExportValue; } const symbol = resolveEntityName(node, meaning, /*ignoreErrors*/ true); return symbol && symbol !== unknownSymbol ? getTypeReferenceDirectivesForSymbol(symbol, meaning) : undefined; } // defined here to avoid outer scope pollution function getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[] | undefined { // program does not have any files with type reference directives - bail out if (!fileToDirective) { return undefined; } if (!isSymbolFromTypeDeclarationFile(symbol)) { return undefined; } // check what declarations in the symbol can contribute to the target meaning let typeReferenceDirectives: string[] | undefined; for (const decl of symbol.declarations) { // check meaning of the local symbol to see if declaration needs to be analyzed further if (decl.symbol && decl.symbol.flags & meaning!) { const file = getSourceFileOfNode(decl); const typeReferenceDirective = fileToDirective.get(file.path); if (typeReferenceDirective) { (typeReferenceDirectives || (typeReferenceDirectives = [])).push(typeReferenceDirective); } else { // found at least one entry that does not originate from type reference directive return undefined; } } } return typeReferenceDirectives; } function isSymbolFromTypeDeclarationFile(symbol: Symbol): boolean { // bail out if symbol does not have associated declarations (i.e. this is transient symbol created for property in binding pattern) if (!symbol.declarations) { return false; } // walk the parent chain for symbols to make sure that top level parent symbol is in the global scope // external modules cannot define or contribute to type declaration files let current = symbol; while (true) { const parent = getParentOfSymbol(current); if (parent) { current = parent; } else { break; } } if (current.valueDeclaration && current.valueDeclaration.kind === SyntaxKind.SourceFile && current.flags & SymbolFlags.ValueModule) { return false; } // check that at least one declaration of top level symbol originates from type declaration file for (const decl of symbol.declarations) { const file = getSourceFileOfNode(decl); if (fileToDirective.has(file.path)) { return true; } } return false; } function addReferencedFilesToTypeDirective(file: SourceFile, key: string) { if (fileToDirective.has(file.path)) return; fileToDirective.set(file.path, key); for (const { fileName } of file.referencedFiles) { const resolvedFile = resolveTripleslashReference(fileName, file.fileName); const referencedFile = host.getSourceFile(resolvedFile); if (referencedFile) { addReferencedFilesToTypeDirective(referencedFile, key); } } } } function getExternalModuleFileFromDeclaration(declaration: AnyImportOrReExport | ModuleDeclaration | ImportTypeNode): SourceFile | undefined { const specifier = declaration.kind === SyntaxKind.ModuleDeclaration ? tryCast(declaration.name, isStringLiteral) : getExternalModuleName(declaration); const moduleSymbol = resolveExternalModuleNameWorker(specifier!, specifier!, /*moduleNotFoundError*/ undefined); // TODO: GH#18217 if (!moduleSymbol) { return undefined; } return getDeclarationOfKind(moduleSymbol, SyntaxKind.SourceFile); } function initializeTypeChecker() { // Bind all source files and propagate errors for (const file of host.getSourceFiles()) { bindSourceFile(file, compilerOptions); } amalgamatedDuplicates = new Map(); // Initialize global symbol table let augmentations: (readonly (StringLiteral | Identifier)[])[] | undefined; for (const file of host.getSourceFiles()) { if (file.redirectInfo) { continue; } if (!isExternalOrCommonJsModule(file)) { // It is an error for a non-external-module (i.e. script) to declare its own `globalThis`. // We can't use `builtinGlobals` for this due to synthetic expando-namespace generation in JS files. const fileGlobalThisSymbol = file.locals!.get("globalThis" as __String); if (fileGlobalThisSymbol) { for (const declaration of fileGlobalThisSymbol.declarations) { diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, "globalThis")); } } mergeSymbolTable(globals, file.locals!); } if (file.jsGlobalAugmentations) { mergeSymbolTable(globals, file.jsGlobalAugmentations); } if (file.patternAmbientModules && file.patternAmbientModules.length) { patternAmbientModules = concatenate(patternAmbientModules, file.patternAmbientModules); } if (file.moduleAugmentations.length) { (augmentations || (augmentations = [])).push(file.moduleAugmentations); } if (file.symbol && file.symbol.globalExports) { // Merge in UMD exports with first-in-wins semantics (see #9771) const source = file.symbol.globalExports; source.forEach((sourceSymbol, id) => { if (!globals.has(id)) { globals.set(id, sourceSymbol); } }); } } // We do global augmentations separately from module augmentations (and before creating global types) because they // 1. Affect global types. We won't have the correct global types until global augmentations are merged. Also, // 2. Module augmentation instantiation requires creating the type of a module, which, in turn, can require // checking for an export or property on the module (if export=) which, in turn, can fall back to the // apparent type of the module - either globalObjectType or globalFunctionType - which wouldn't exist if we // did module augmentations prior to finalizing the global types. if (augmentations) { // merge _global_ module augmentations. // this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed for (const list of augmentations) { for (const augmentation of list) { if (!isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)) continue; mergeModuleAugmentation(augmentation); } } } // Setup global builtins addToSymbolTable(globals, builtinGlobals, Diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0); getSymbolLinks(undefinedSymbol).type = undefinedWideningType; getSymbolLinks(argumentsSymbol).type = getGlobalType("IArguments" as __String, /*arity*/ 0, /*reportErrors*/ true); getSymbolLinks(unknownSymbol).type = errorType; getSymbolLinks(globalThisSymbol).type = createObjectType(ObjectFlags.Anonymous, globalThisSymbol); // Initialize special types globalArrayType = getGlobalType("Array" as __String, /*arity*/ 1, /*reportErrors*/ true); globalObjectType = getGlobalType("Object" as __String, /*arity*/ 0, /*reportErrors*/ true); globalFunctionType = getGlobalType("Function" as __String, /*arity*/ 0, /*reportErrors*/ true); globalCallableFunctionType = strictBindCallApply && getGlobalType("CallableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType; globalNewableFunctionType = strictBindCallApply && getGlobalType("NewableFunction" as __String, /*arity*/ 0, /*reportErrors*/ true) || globalFunctionType; globalStringType = getGlobalType("String" as __String, /*arity*/ 0, /*reportErrors*/ true); globalNumberType = getGlobalType("Number" as __String, /*arity*/ 0, /*reportErrors*/ true); globalBooleanType = getGlobalType("Boolean" as __String, /*arity*/ 0, /*reportErrors*/ true); globalRegExpType = getGlobalType("RegExp" as __String, /*arity*/ 0, /*reportErrors*/ true); anyArrayType = createArrayType(anyType); autoArrayType = createArrayType(autoType); if (autoArrayType === emptyObjectType) { // autoArrayType is used as a marker, so even if global Array type is not defined, it needs to be a unique type autoArrayType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); } globalReadonlyArrayType = getGlobalTypeOrUndefined("ReadonlyArray" as __String, /*arity*/ 1) || globalArrayType; anyReadonlyArrayType = globalReadonlyArrayType ? createTypeFromGenericGlobalType(globalReadonlyArrayType, [anyType]) : anyArrayType; globalThisType = getGlobalTypeOrUndefined("ThisType" as __String, /*arity*/ 1); if (augmentations) { // merge _nonglobal_ module augmentations. // this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed for (const list of augmentations) { for (const augmentation of list) { if (isGlobalScopeAugmentation(augmentation.parent as ModuleDeclaration)) continue; mergeModuleAugmentation(augmentation); } } } amalgamatedDuplicates.forEach(({ firstFile, secondFile, conflictingSymbols }) => { // If not many things conflict, issue individual errors if (conflictingSymbols.size < 8) { conflictingSymbols.forEach(({ isBlockScoped, firstFileLocations, secondFileLocations }, symbolName) => { const message = isBlockScoped ? Diagnostics.Cannot_redeclare_block_scoped_variable_0 : Diagnostics.Duplicate_identifier_0; for (const node of firstFileLocations) { addDuplicateDeclarationError(node, message, symbolName, secondFileLocations); } for (const node of secondFileLocations) { addDuplicateDeclarationError(node, message, symbolName, firstFileLocations); } }); } else { // Otherwise issue top-level error since the files appear very identical in terms of what they contain const list = arrayFrom(conflictingSymbols.keys()).join(", "); diagnostics.add(addRelatedInfo( createDiagnosticForNode(firstFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list), createDiagnosticForNode(secondFile, Diagnostics.Conflicts_are_in_this_file) )); diagnostics.add(addRelatedInfo( createDiagnosticForNode(secondFile, Diagnostics.Definitions_of_the_following_identifiers_conflict_with_those_in_another_file_Colon_0, list), createDiagnosticForNode(firstFile, Diagnostics.Conflicts_are_in_this_file) )); } }); amalgamatedDuplicates = undefined; } function checkExternalEmitHelpers(location: Node, helpers: ExternalEmitHelpers) { if ((requestedExternalEmitHelpers & helpers) !== helpers && compilerOptions.importHelpers) { const sourceFile = getSourceFileOfNode(location); if (isEffectiveExternalModule(sourceFile, compilerOptions) && !(location.flags & NodeFlags.Ambient)) { const helpersModule = resolveHelpersModule(sourceFile, location); if (helpersModule !== unknownSymbol) { const uncheckedHelpers = helpers & ~requestedExternalEmitHelpers; for (let helper = ExternalEmitHelpers.FirstEmitHelper; helper <= ExternalEmitHelpers.LastEmitHelper; helper <<= 1) { if (uncheckedHelpers & helper) { const name = getHelperName(helper); const symbol = getSymbol(helpersModule.exports!, escapeLeadingUnderscores(name), SymbolFlags.Value); if (!symbol) { error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_which_does_not_exist_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name); } } } } requestedExternalEmitHelpers |= helpers; } } } function getHelperName(helper: ExternalEmitHelpers) { switch (helper) { case ExternalEmitHelpers.Extends: return "__extends"; case ExternalEmitHelpers.Assign: return "__assign"; case ExternalEmitHelpers.Rest: return "__rest"; case ExternalEmitHelpers.Decorate: return "__decorate"; case ExternalEmitHelpers.Metadata: return "__metadata"; case ExternalEmitHelpers.Param: return "__param"; case ExternalEmitHelpers.Awaiter: return "__awaiter"; case ExternalEmitHelpers.Generator: return "__generator"; case ExternalEmitHelpers.Values: return "__values"; case ExternalEmitHelpers.Read: return "__read"; case ExternalEmitHelpers.Spread: return "__spread"; case ExternalEmitHelpers.SpreadArrays: return "__spreadArrays"; case ExternalEmitHelpers.Await: return "__await"; case ExternalEmitHelpers.AsyncGenerator: return "__asyncGenerator"; case ExternalEmitHelpers.AsyncDelegator: return "__asyncDelegator"; case ExternalEmitHelpers.AsyncValues: return "__asyncValues"; case ExternalEmitHelpers.ExportStar: return "__exportStar"; case ExternalEmitHelpers.ImportStar: return "__importStar"; case ExternalEmitHelpers.ImportDefault: return "__importDefault"; case ExternalEmitHelpers.MakeTemplateObject: return "__makeTemplateObject"; case ExternalEmitHelpers.ClassPrivateFieldGet: return "__classPrivateFieldGet"; case ExternalEmitHelpers.ClassPrivateFieldSet: return "__classPrivateFieldSet"; case ExternalEmitHelpers.CreateBinding: return "__createBinding"; default: return Debug.fail("Unrecognized helper"); } } function resolveHelpersModule(node: SourceFile, errorNode: Node) { if (!externalHelpersModule) { externalHelpersModule = resolveExternalModule(node, externalHelpersModuleNameText, Diagnostics.This_syntax_requires_an_imported_helper_but_module_0_cannot_be_found, errorNode) || unknownSymbol; } return externalHelpersModule; } // GRAMMAR CHECKING function checkGrammarDecoratorsAndModifiers(node: Node): boolean { return checkGrammarDecorators(node) || checkGrammarModifiers(node); } function checkGrammarDecorators(node: Node): boolean { if (!node.decorators) { return false; } if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) { if (node.kind === SyntaxKind.MethodDeclaration && !nodeIsPresent((node).body)) { return grammarErrorOnFirstToken(node, Diagnostics.A_decorator_can_only_decorate_a_method_implementation_not_an_overload); } else { return grammarErrorOnFirstToken(node, Diagnostics.Decorators_are_not_valid_here); } } else if (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor) { const accessors = getAllAccessorDeclarations((node.parent).members, node); if (accessors.firstAccessor.decorators && node === accessors.secondAccessor) { return grammarErrorOnFirstToken(node, Diagnostics.Decorators_cannot_be_applied_to_multiple_get_Slashset_accessors_of_the_same_name); } } return false; } function checkGrammarModifiers(node: Node): boolean { const quickResult = reportObviousModifierErrors(node); if (quickResult !== undefined) { return quickResult; } let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastReadonly: Node | undefined; let flags = ModifierFlags.None; for (const modifier of node.modifiers!) { if (modifier.kind !== SyntaxKind.ReadonlyKeyword) { if (node.kind === SyntaxKind.PropertySignature || node.kind === SyntaxKind.MethodSignature) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_member, tokenToString(modifier.kind)); } if (node.kind === SyntaxKind.IndexSignature) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_index_signature, tokenToString(modifier.kind)); } } switch (modifier.kind) { case SyntaxKind.ConstKeyword: if (node.kind !== SyntaxKind.EnumDeclaration) { return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword)); } break; case SyntaxKind.PublicKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.PrivateKeyword: const text = visibilityToString(modifierToFlag(modifier.kind)); if (flags & ModifierFlags.AccessibilityModifier) { return grammarErrorOnNode(modifier, Diagnostics.Accessibility_modifier_already_seen); } else if (flags & ModifierFlags.Static) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "static"); } else if (flags & ModifierFlags.Readonly) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "readonly"); } else if (flags & ModifierFlags.Async) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "async"); } else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, text); } else if (flags & ModifierFlags.Abstract) { if (modifier.kind === SyntaxKind.PrivateKeyword) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, text, "abstract"); } else { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "abstract"); } } else if (isPrivateIdentifierPropertyDeclaration(node)) { return grammarErrorOnNode(modifier, Diagnostics.An_accessibility_modifier_cannot_be_used_with_a_private_identifier); } flags |= modifierToFlag(modifier.kind); break; case SyntaxKind.StaticKeyword: if (flags & ModifierFlags.Static) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "static"); } else if (flags & ModifierFlags.Readonly) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "readonly"); } else if (flags & ModifierFlags.Async) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "async"); } else if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_module_or_namespace_element, "static"); } else if (node.kind === SyntaxKind.Parameter) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "static"); } else if (flags & ModifierFlags.Abstract) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract"); } else if (isPrivateIdentifierPropertyDeclaration(node)) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "static"); } flags |= ModifierFlags.Static; lastStatic = modifier; break; case SyntaxKind.ReadonlyKeyword: if (flags & ModifierFlags.Readonly) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "readonly"); } else if (node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.PropertySignature && node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.Parameter) { // If node.kind === SyntaxKind.Parameter, checkParameter report an error if it's not a parameter property. return grammarErrorOnNode(modifier, Diagnostics.readonly_modifier_can_only_appear_on_a_property_declaration_or_index_signature); } flags |= ModifierFlags.Readonly; lastReadonly = modifier; break; case SyntaxKind.ExportKeyword: if (flags & ModifierFlags.Export) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "export"); } else if (flags & ModifierFlags.Ambient) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "declare"); } else if (flags & ModifierFlags.Abstract) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "abstract"); } else if (flags & ModifierFlags.Async) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "async"); } else if (isClassLike(node.parent)) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_class_elements_of_this_kind, "export"); } else if (node.kind === SyntaxKind.Parameter) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "export"); } flags |= ModifierFlags.Export; break; case SyntaxKind.DefaultKeyword: const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : node.parent.parent; if (container.kind === SyntaxKind.ModuleDeclaration && !isAmbientModule(container)) { return grammarErrorOnNode(modifier, Diagnostics.A_default_export_can_only_be_used_in_an_ECMAScript_style_module); } flags |= ModifierFlags.Default; break; case SyntaxKind.DeclareKeyword: if (flags & ModifierFlags.Ambient) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "declare"); } else if (flags & ModifierFlags.Async) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async"); } else if (isClassLike(node.parent) && !isPropertyDeclaration(node)) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_class_elements_of_this_kind, "declare"); } else if (node.kind === SyntaxKind.Parameter) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "declare"); } else if ((node.parent.flags & NodeFlags.Ambient) && node.parent.kind === SyntaxKind.ModuleBlock) { return grammarErrorOnNode(modifier, Diagnostics.A_declare_modifier_cannot_be_used_in_an_already_ambient_context); } else if (isPrivateIdentifierPropertyDeclaration(node)) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "declare"); } flags |= ModifierFlags.Ambient; lastDeclare = modifier; break; case SyntaxKind.AbstractKeyword: if (flags & ModifierFlags.Abstract) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "abstract"); } if (node.kind !== SyntaxKind.ClassDeclaration) { if (node.kind !== SyntaxKind.MethodDeclaration && node.kind !== SyntaxKind.PropertyDeclaration && node.kind !== SyntaxKind.GetAccessor && node.kind !== SyntaxKind.SetAccessor) { return grammarErrorOnNode(modifier, Diagnostics.abstract_modifier_can_only_appear_on_a_class_method_or_property_declaration); } if (!(node.parent.kind === SyntaxKind.ClassDeclaration && hasSyntacticModifier(node.parent, ModifierFlags.Abstract))) { return grammarErrorOnNode(modifier, Diagnostics.Abstract_methods_can_only_appear_within_an_abstract_class); } if (flags & ModifierFlags.Static) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "abstract"); } if (flags & ModifierFlags.Private) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "private", "abstract"); } if (flags & ModifierFlags.Async && lastAsync) { return grammarErrorOnNode(lastAsync, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "async", "abstract"); } } if (isNamedDeclaration(node) && node.name.kind === SyntaxKind.PrivateIdentifier) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "abstract"); } flags |= ModifierFlags.Abstract; break; case SyntaxKind.AsyncKeyword: if (flags & ModifierFlags.Async) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "async"); } else if (flags & ModifierFlags.Ambient || node.parent.flags & NodeFlags.Ambient) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async"); } else if (node.kind === SyntaxKind.Parameter) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "async"); } if (flags & ModifierFlags.Abstract) { return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "async", "abstract"); } flags |= ModifierFlags.Async; lastAsync = modifier; break; } } if (node.kind === SyntaxKind.Constructor) { if (flags & ModifierFlags.Static) { return grammarErrorOnNode(lastStatic!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "static"); } if (flags & ModifierFlags.Abstract) { return grammarErrorOnNode(lastStatic!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "abstract"); // TODO: GH#18217 } else if (flags & ModifierFlags.Async) { return grammarErrorOnNode(lastAsync!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async"); } else if (flags & ModifierFlags.Readonly) { return grammarErrorOnNode(lastReadonly!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "readonly"); } return false; } else if ((node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration) && flags & ModifierFlags.Ambient) { return grammarErrorOnNode(lastDeclare!, Diagnostics.A_0_modifier_cannot_be_used_with_an_import_declaration, "declare"); } else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.ParameterPropertyModifier) && isBindingPattern((node).name)) { return grammarErrorOnNode(node, Diagnostics.A_parameter_property_may_not_be_declared_using_a_binding_pattern); } else if (node.kind === SyntaxKind.Parameter && (flags & ModifierFlags.ParameterPropertyModifier) && (node).dotDotDotToken) { return grammarErrorOnNode(node, Diagnostics.A_parameter_property_cannot_be_declared_using_a_rest_parameter); } if (flags & ModifierFlags.Async) { return checkGrammarAsyncModifier(node, lastAsync!); } return false; } /** * true | false: Early return this value from checkGrammarModifiers. * undefined: Need to do full checking on the modifiers. */ function reportObviousModifierErrors(node: Node): boolean | undefined { return !node.modifiers ? false : shouldReportBadModifier(node) ? grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here) : undefined; } function shouldReportBadModifier(node: Node): boolean { switch (node.kind) { case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.Constructor: case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: case SyntaxKind.IndexSignature: case SyntaxKind.ModuleDeclaration: case SyntaxKind.ImportDeclaration: case SyntaxKind.ImportEqualsDeclaration: case SyntaxKind.ExportDeclaration: case SyntaxKind.ExportAssignment: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.Parameter: return false; default: if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { return false; } switch (node.kind) { case SyntaxKind.FunctionDeclaration: return nodeHasAnyModifiersExcept(node, SyntaxKind.AsyncKeyword); case SyntaxKind.ClassDeclaration: return nodeHasAnyModifiersExcept(node, SyntaxKind.AbstractKeyword); case SyntaxKind.InterfaceDeclaration: case SyntaxKind.VariableStatement: case SyntaxKind.TypeAliasDeclaration: return true; case SyntaxKind.EnumDeclaration: return nodeHasAnyModifiersExcept(node, SyntaxKind.ConstKeyword); default: Debug.fail(); return false; } } } function nodeHasAnyModifiersExcept(node: Node, allowedModifier: SyntaxKind): boolean { return node.modifiers!.length > 1 || node.modifiers![0].kind !== allowedModifier; } function checkGrammarAsyncModifier(node: Node, asyncModifier: Node): boolean { switch (node.kind) { case SyntaxKind.MethodDeclaration: case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: return false; } return grammarErrorOnNode(asyncModifier, Diagnostics._0_modifier_cannot_be_used_here, "async"); } function checkGrammarForDisallowedTrailingComma(list: NodeArray | undefined, diag = Diagnostics.Trailing_comma_not_allowed): boolean { if (list && list.hasTrailingComma) { return grammarErrorAtPos(list[0], list.end - ",".length, ",".length, diag); } return false; } function checkGrammarTypeParameterList(typeParameters: NodeArray | undefined, file: SourceFile): boolean { if (typeParameters && typeParameters.length === 0) { const start = typeParameters.pos - "<".length; const end = skipTrivia(file.text, typeParameters.end) + ">".length; return grammarErrorAtPos(file, start, end - start, Diagnostics.Type_parameter_list_cannot_be_empty); } return false; } function checkGrammarParameterList(parameters: NodeArray) { let seenOptionalParameter = false; const parameterCount = parameters.length; for (let i = 0; i < parameterCount; i++) { const parameter = parameters[i]; if (parameter.dotDotDotToken) { if (i !== (parameterCount - 1)) { return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); } if (!(parameter.flags & NodeFlags.Ambient)) { // Allow `...foo,` in ambient declarations; see GH#23070 checkGrammarForDisallowedTrailingComma(parameters, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); } if (parameter.questionToken) { return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_rest_parameter_cannot_be_optional); } if (parameter.initializer) { return grammarErrorOnNode(parameter.name, Diagnostics.A_rest_parameter_cannot_have_an_initializer); } } else if (isOptionalParameter(parameter)) { seenOptionalParameter = true; if (parameter.questionToken && parameter.initializer) { return grammarErrorOnNode(parameter.name, Diagnostics.Parameter_cannot_have_question_mark_and_initializer); } } else if (seenOptionalParameter && !parameter.initializer) { return grammarErrorOnNode(parameter.name, Diagnostics.A_required_parameter_cannot_follow_an_optional_parameter); } } } function getNonSimpleParameters(parameters: readonly ParameterDeclaration[]): readonly ParameterDeclaration[] { return filter(parameters, parameter => !!parameter.initializer || isBindingPattern(parameter.name) || isRestParameter(parameter)); } function checkGrammarForUseStrictSimpleParameterList(node: FunctionLikeDeclaration): boolean { if (languageVersion >= ScriptTarget.ES2016) { const useStrictDirective = node.body && isBlock(node.body) && findUseStrictPrologue(node.body.statements); if (useStrictDirective) { const nonSimpleParameters = getNonSimpleParameters(node.parameters); if (length(nonSimpleParameters)) { forEach(nonSimpleParameters, parameter => { addRelatedInfo( error(parameter, Diagnostics.This_parameter_is_not_allowed_with_use_strict_directive), createDiagnosticForNode(useStrictDirective, Diagnostics.use_strict_directive_used_here) ); }); const diagnostics = nonSimpleParameters.map((parameter, index) => ( index === 0 ? createDiagnosticForNode(parameter, Diagnostics.Non_simple_parameter_declared_here) : createDiagnosticForNode(parameter, Diagnostics.and_here) )) as [DiagnosticWithLocation, ...DiagnosticWithLocation[]]; addRelatedInfo(error(useStrictDirective, Diagnostics.use_strict_directive_cannot_be_used_with_non_simple_parameter_list), ...diagnostics); return true; } } } return false; } function checkGrammarFunctionLikeDeclaration(node: FunctionLikeDeclaration | MethodSignature): boolean { // Prevent cascading error by short-circuit const file = getSourceFileOfNode(node); return checkGrammarDecoratorsAndModifiers(node) || checkGrammarTypeParameterList(node.typeParameters, file) || checkGrammarParameterList(node.parameters) || checkGrammarArrowFunction(node, file) || (isFunctionLikeDeclaration(node) && checkGrammarForUseStrictSimpleParameterList(node)); } function checkGrammarClassLikeDeclaration(node: ClassLikeDeclaration): boolean { const file = getSourceFileOfNode(node); return checkGrammarClassDeclarationHeritageClauses(node) || checkGrammarTypeParameterList(node.typeParameters, file); } function checkGrammarArrowFunction(node: Node, file: SourceFile): boolean { if (!isArrowFunction(node)) { return false; } const { equalsGreaterThanToken } = node; const startLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.pos).line; const endLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.end).line; return startLine !== endLine && grammarErrorOnNode(equalsGreaterThanToken, Diagnostics.Line_terminator_not_permitted_before_arrow); } function checkGrammarIndexSignatureParameters(node: SignatureDeclaration): boolean { const parameter = node.parameters[0]; if (node.parameters.length !== 1) { if (parameter) { return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_must_have_exactly_one_parameter); } else { return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_exactly_one_parameter); } } checkGrammarForDisallowedTrailingComma(node.parameters, Diagnostics.An_index_signature_cannot_have_a_trailing_comma); if (parameter.dotDotDotToken) { return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.An_index_signature_cannot_have_a_rest_parameter); } if (hasEffectiveModifiers(parameter)) { return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_accessibility_modifier); } if (parameter.questionToken) { return grammarErrorOnNode(parameter.questionToken, Diagnostics.An_index_signature_parameter_cannot_have_a_question_mark); } if (parameter.initializer) { return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_initializer); } if (!parameter.type) { return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_must_have_a_type_annotation); } if (parameter.type.kind !== SyntaxKind.StringKeyword && parameter.type.kind !== SyntaxKind.NumberKeyword) { const type = getTypeFromTypeNode(parameter.type); if (type.flags & TypeFlags.String || type.flags & TypeFlags.Number) { return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_cannot_be_a_type_alias_Consider_writing_0_Colon_1_Colon_2_instead, getTextOfNode(parameter.name), typeToString(type), typeToString(node.type ? getTypeFromTypeNode(node.type) : anyType)); } if (type.flags & TypeFlags.Union && allTypesAssignableToKind(type, TypeFlags.StringOrNumberLiteral, /*strict*/ true)) { return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_cannot_be_a_union_type_Consider_using_a_mapped_object_type_instead); } return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_must_be_either_string_or_number); } if (!node.type) { return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_a_type_annotation); } return false; } function checkGrammarIndexSignature(node: SignatureDeclaration) { // Prevent cascading error by short-circuit return checkGrammarDecoratorsAndModifiers(node) || checkGrammarIndexSignatureParameters(node); } function checkGrammarForAtLeastOneTypeArgument(node: Node, typeArguments: NodeArray | undefined): boolean { if (typeArguments && typeArguments.length === 0) { const sourceFile = getSourceFileOfNode(node); const start = typeArguments.pos - "<".length; const end = skipTrivia(sourceFile.text, typeArguments.end) + ">".length; return grammarErrorAtPos(sourceFile, start, end - start, Diagnostics.Type_argument_list_cannot_be_empty); } return false; } function checkGrammarTypeArguments(node: Node, typeArguments: NodeArray | undefined): boolean { return checkGrammarForDisallowedTrailingComma(typeArguments) || checkGrammarForAtLeastOneTypeArgument(node, typeArguments); } function checkGrammarTaggedTemplateChain(node: TaggedTemplateExpression): boolean { if (node.questionDotToken || node.flags & NodeFlags.OptionalChain) { return grammarErrorOnNode(node.template, Diagnostics.Tagged_template_expressions_are_not_permitted_in_an_optional_chain); } return false; } function checkGrammarForOmittedArgument(args: NodeArray | undefined): boolean { if (args) { for (const arg of args) { if (arg.kind === SyntaxKind.OmittedExpression) { return grammarErrorAtPos(arg, arg.pos, 0, Diagnostics.Argument_expression_expected); } } } return false; } function checkGrammarArguments(args: NodeArray | undefined): boolean { return checkGrammarForOmittedArgument(args); } function checkGrammarHeritageClause(node: HeritageClause): boolean { const types = node.types; if (checkGrammarForDisallowedTrailingComma(types)) { return true; } if (types && types.length === 0) { const listType = tokenToString(node.token); return grammarErrorAtPos(node, types.pos, 0, Diagnostics._0_list_cannot_be_empty, listType); } return some(types, checkGrammarExpressionWithTypeArguments); } function checkGrammarExpressionWithTypeArguments(node: ExpressionWithTypeArguments) { return checkGrammarTypeArguments(node, node.typeArguments); } function checkGrammarClassDeclarationHeritageClauses(node: ClassLikeDeclaration) { let seenExtendsClause = false; let seenImplementsClause = false; if (!checkGrammarDecoratorsAndModifiers(node) && node.heritageClauses) { for (const heritageClause of node.heritageClauses) { if (heritageClause.token === SyntaxKind.ExtendsKeyword) { if (seenExtendsClause) { return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); } if (seenImplementsClause) { return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_must_precede_implements_clause); } if (heritageClause.types.length > 1) { return grammarErrorOnFirstToken(heritageClause.types[1], Diagnostics.Classes_can_only_extend_a_single_class); } seenExtendsClause = true; } else { Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); if (seenImplementsClause) { return grammarErrorOnFirstToken(heritageClause, Diagnostics.implements_clause_already_seen); } seenImplementsClause = true; } // Grammar checking heritageClause inside class declaration checkGrammarHeritageClause(heritageClause); } } } function checkGrammarInterfaceDeclaration(node: InterfaceDeclaration) { let seenExtendsClause = false; if (node.heritageClauses) { for (const heritageClause of node.heritageClauses) { if (heritageClause.token === SyntaxKind.ExtendsKeyword) { if (seenExtendsClause) { return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); } seenExtendsClause = true; } else { Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); return grammarErrorOnFirstToken(heritageClause, Diagnostics.Interface_declaration_cannot_have_implements_clause); } // Grammar checking heritageClause inside class declaration checkGrammarHeritageClause(heritageClause); } } return false; } function checkGrammarComputedPropertyName(node: Node): boolean { // If node is not a computedPropertyName, just skip the grammar checking if (node.kind !== SyntaxKind.ComputedPropertyName) { return false; } const computedPropertyName = node; if (computedPropertyName.expression.kind === SyntaxKind.BinaryExpression && (computedPropertyName.expression).operatorToken.kind === SyntaxKind.CommaToken) { return grammarErrorOnNode(computedPropertyName.expression, Diagnostics.A_comma_expression_is_not_allowed_in_a_computed_property_name); } return false; } function checkGrammarForGenerator(node: FunctionLikeDeclaration) { if (node.asteriskToken) { Debug.assert( node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.MethodDeclaration); if (node.flags & NodeFlags.Ambient) { return grammarErrorOnNode(node.asteriskToken, Diagnostics.Generators_are_not_allowed_in_an_ambient_context); } if (!node.body) { return grammarErrorOnNode(node.asteriskToken, Diagnostics.An_overload_signature_cannot_be_declared_as_a_generator); } } } function checkGrammarForInvalidQuestionMark(questionToken: QuestionToken | undefined, message: DiagnosticMessage): boolean { return !!questionToken && grammarErrorOnNode(questionToken, message); } function checkGrammarForInvalidExclamationToken(exclamationToken: ExclamationToken | undefined, message: DiagnosticMessage): boolean { return !!exclamationToken && grammarErrorOnNode(exclamationToken, message); } function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) { const seen = new Map<__String, DeclarationMeaning>(); for (const prop of node.properties) { if (prop.kind === SyntaxKind.SpreadAssignment) { if (inDestructuring) { // a rest property cannot be destructured any further const expression = skipParentheses(prop.expression); if (isArrayLiteralExpression(expression) || isObjectLiteralExpression(expression)) { return grammarErrorOnNode(prop.expression, Diagnostics.A_rest_element_cannot_contain_a_binding_pattern); } } continue; } const name = prop.name; if (name.kind === SyntaxKind.ComputedPropertyName) { // If the name is not a ComputedPropertyName, the grammar checking will skip it checkGrammarComputedPropertyName(name); } if (prop.kind === SyntaxKind.ShorthandPropertyAssignment && !inDestructuring && prop.objectAssignmentInitializer) { // having objectAssignmentInitializer is only valid in ObjectAssignmentPattern // outside of destructuring it is a syntax error return grammarErrorOnNode(prop.equalsToken!, Diagnostics.Did_you_mean_to_use_a_Colon_An_can_only_follow_a_property_name_when_the_containing_object_literal_is_part_of_a_destructuring_pattern); } if (name.kind === SyntaxKind.PrivateIdentifier) { return grammarErrorOnNode(name, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); } // Modifiers are never allowed on properties except for 'async' on a method declaration if (prop.modifiers) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion for (const mod of prop.modifiers!) { // TODO: GH#19955 if (mod.kind !== SyntaxKind.AsyncKeyword || prop.kind !== SyntaxKind.MethodDeclaration) { grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); } } } // ECMA-262 11.1.5 Object Initializer // If previous is not undefined then throw a SyntaxError exception if any of the following conditions are true // a.This production is contained in strict code and IsDataDescriptor(previous) is true and // IsDataDescriptor(propId.descriptor) is true. // b.IsDataDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true. // c.IsAccessorDescriptor(previous) is true and IsDataDescriptor(propId.descriptor) is true. // d.IsAccessorDescriptor(previous) is true and IsAccessorDescriptor(propId.descriptor) is true // and either both previous and propId.descriptor have[[Get]] fields or both previous and propId.descriptor have[[Set]] fields let currentKind: DeclarationMeaning; switch (prop.kind) { case SyntaxKind.ShorthandPropertyAssignment: checkGrammarForInvalidExclamationToken(prop.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context); // falls through case SyntaxKind.PropertyAssignment: // Grammar checking for computedPropertyName and shorthandPropertyAssignment checkGrammarForInvalidQuestionMark(prop.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional); if (name.kind === SyntaxKind.NumericLiteral) { checkGrammarNumericLiteral(name); } currentKind = DeclarationMeaning.PropertyAssignment; break; case SyntaxKind.MethodDeclaration: currentKind = DeclarationMeaning.Method; break; case SyntaxKind.GetAccessor: currentKind = DeclarationMeaning.GetAccessor; break; case SyntaxKind.SetAccessor: currentKind = DeclarationMeaning.SetAccessor; break; default: throw Debug.assertNever(prop, "Unexpected syntax kind:" + (prop).kind); } if (!inDestructuring) { const effectiveName = getPropertyNameForPropertyNameNode(name); if (effectiveName === undefined) { continue; } const existingKind = seen.get(effectiveName); if (!existingKind) { seen.set(effectiveName, currentKind); } else { if ((currentKind & DeclarationMeaning.PropertyAssignmentOrMethod) && (existingKind & DeclarationMeaning.PropertyAssignmentOrMethod)) { grammarErrorOnNode(name, Diagnostics.Duplicate_identifier_0, getTextOfNode(name)); } else if ((currentKind & DeclarationMeaning.GetOrSetAccessor) && (existingKind & DeclarationMeaning.GetOrSetAccessor)) { if (existingKind !== DeclarationMeaning.GetOrSetAccessor && currentKind !== existingKind) { seen.set(effectiveName, currentKind | existingKind); } else { return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_multiple_get_Slashset_accessors_with_the_same_name); } } else { return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_property_and_accessor_with_the_same_name); } } } } } function checkGrammarJsxElement(node: JsxOpeningLikeElement) { checkGrammarTypeArguments(node, node.typeArguments); const seen = new Map<__String, boolean>(); for (const attr of node.attributes.properties) { if (attr.kind === SyntaxKind.JsxSpreadAttribute) { continue; } const { name, initializer } = attr; if (!seen.get(name.escapedText)) { seen.set(name.escapedText, true); } else { return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name); } if (initializer && initializer.kind === SyntaxKind.JsxExpression && !initializer.expression) { return grammarErrorOnNode(initializer, Diagnostics.JSX_attributes_must_only_be_assigned_a_non_empty_expression); } } } function checkGrammarJsxExpression(node: JsxExpression) { if (node.expression && isCommaSequence(node.expression)) { return grammarErrorOnNode(node.expression, Diagnostics.JSX_expressions_may_not_use_the_comma_operator_Did_you_mean_to_write_an_array); } } function checkGrammarForInOrForOfStatement(forInOrOfStatement: ForInOrOfStatement): boolean { if (checkGrammarStatementInAmbientContext(forInOrOfStatement)) { return true; } if (forInOrOfStatement.kind === SyntaxKind.ForOfStatement && forInOrOfStatement.awaitModifier) { if ((forInOrOfStatement.flags & NodeFlags.AwaitContext) === NodeFlags.None) { // use of 'for-await-of' in non-async function const sourceFile = getSourceFileOfNode(forInOrOfStatement); if (!hasParseDiagnostics(sourceFile)) { const diagnostic = createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.A_for_await_of_statement_is_only_allowed_within_an_async_function_or_async_generator); const func = getContainingFunction(forInOrOfStatement); if (func && func.kind !== SyntaxKind.Constructor) { Debug.assert((getFunctionFlags(func) & FunctionFlags.Async) === 0, "Enclosing function should never be an async function."); const relatedInfo = createDiagnosticForNode(func, Diagnostics.Did_you_mean_to_mark_this_function_as_async); addRelatedInfo(diagnostic, relatedInfo); } diagnostics.add(diagnostic); return true; } return false; } } if (forInOrOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList) { const variableList = forInOrOfStatement.initializer; if (!checkGrammarVariableDeclarationList(variableList)) { const declarations = variableList.declarations; // declarations.length can be zero if there is an error in variable declaration in for-of or for-in // See http://www.ecma-international.org/ecma-262/6.0/#sec-for-in-and-for-of-statements for details // For example: // var let = 10; // for (let of [1,2,3]) {} // this is invalid ES6 syntax // for (let in [1,2,3]) {} // this is invalid ES6 syntax // We will then want to skip on grammar checking on variableList declaration if (!declarations.length) { return false; } if (declarations.length > 1) { const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement ? Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_in_statement : Diagnostics.Only_a_single_variable_declaration_is_allowed_in_a_for_of_statement; return grammarErrorOnFirstToken(variableList.declarations[1], diagnostic); } const firstDeclaration = declarations[0]; if (firstDeclaration.initializer) { const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement ? Diagnostics.The_variable_declaration_of_a_for_in_statement_cannot_have_an_initializer : Diagnostics.The_variable_declaration_of_a_for_of_statement_cannot_have_an_initializer; return grammarErrorOnNode(firstDeclaration.name, diagnostic); } if (firstDeclaration.type) { const diagnostic = forInOrOfStatement.kind === SyntaxKind.ForInStatement ? Diagnostics.The_left_hand_side_of_a_for_in_statement_cannot_use_a_type_annotation : Diagnostics.The_left_hand_side_of_a_for_of_statement_cannot_use_a_type_annotation; return grammarErrorOnNode(firstDeclaration, diagnostic); } } } return false; } function checkGrammarAccessor(accessor: AccessorDeclaration): boolean { if (!(accessor.flags & NodeFlags.Ambient)) { if (languageVersion < ScriptTarget.ES5) { return grammarErrorOnNode(accessor.name, Diagnostics.Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher); } if (accessor.body === undefined && !hasSyntacticModifier(accessor, ModifierFlags.Abstract)) { return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{"); } } if (accessor.body && hasSyntacticModifier(accessor, ModifierFlags.Abstract)) { return grammarErrorOnNode(accessor, Diagnostics.An_abstract_accessor_cannot_have_an_implementation); } if (accessor.typeParameters) { return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_have_type_parameters); } if (!doesAccessorHaveCorrectParameterCount(accessor)) { return grammarErrorOnNode(accessor.name, accessor.kind === SyntaxKind.GetAccessor ? Diagnostics.A_get_accessor_cannot_have_parameters : Diagnostics.A_set_accessor_must_have_exactly_one_parameter); } if (accessor.kind === SyntaxKind.SetAccessor) { if (accessor.type) { return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_cannot_have_a_return_type_annotation); } const parameter = Debug.checkDefined(getSetAccessorValueParameter(accessor), "Return value does not match parameter count assertion."); if (parameter.dotDotDotToken) { return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_set_accessor_cannot_have_rest_parameter); } if (parameter.questionToken) { return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_set_accessor_cannot_have_an_optional_parameter); } if (parameter.initializer) { return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_parameter_cannot_have_an_initializer); } } return false; } /** Does the accessor have the right number of parameters? * A get accessor has no parameters or a single `this` parameter. * A set accessor has one parameter or a `this` parameter and one more parameter. */ function doesAccessorHaveCorrectParameterCount(accessor: AccessorDeclaration) { return getAccessorThisParameter(accessor) || accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 0 : 1); } function getAccessorThisParameter(accessor: AccessorDeclaration): ParameterDeclaration | undefined { if (accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 1 : 2)) { return getThisParameter(accessor); } } function checkGrammarTypeOperatorNode(node: TypeOperatorNode) { if (node.operator === SyntaxKind.UniqueKeyword) { if (node.type.kind !== SyntaxKind.SymbolKeyword) { return grammarErrorOnNode(node.type, Diagnostics._0_expected, tokenToString(SyntaxKind.SymbolKeyword)); } let parent = walkUpParenthesizedTypes(node.parent); if (isInJSFile(parent) && isJSDocTypeExpression(parent)) { parent = parent.parent; if (isJSDocTypeTag(parent)) { // walk up past JSDoc comment node parent = parent.parent.parent; } } switch (parent.kind) { case SyntaxKind.VariableDeclaration: const decl = parent as VariableDeclaration; if (decl.name.kind !== SyntaxKind.Identifier) { return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_may_not_be_used_on_a_variable_declaration_with_a_binding_name); } if (!isVariableDeclarationInVariableStatement(decl)) { return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_only_allowed_on_variables_in_a_variable_statement); } if (!(decl.parent.flags & NodeFlags.Const)) { return grammarErrorOnNode((parent).name, Diagnostics.A_variable_whose_type_is_a_unique_symbol_type_must_be_const); } break; case SyntaxKind.PropertyDeclaration: if (!hasSyntacticModifier(parent, ModifierFlags.Static) || !hasEffectiveModifier(parent, ModifierFlags.Readonly)) { return grammarErrorOnNode((parent).name, Diagnostics.A_property_of_a_class_whose_type_is_a_unique_symbol_type_must_be_both_static_and_readonly); } break; case SyntaxKind.PropertySignature: if (!hasSyntacticModifier(parent, ModifierFlags.Readonly)) { return grammarErrorOnNode((parent).name, Diagnostics.A_property_of_an_interface_or_type_literal_whose_type_is_a_unique_symbol_type_must_be_readonly); } break; default: return grammarErrorOnNode(node, Diagnostics.unique_symbol_types_are_not_allowed_here); } } else if (node.operator === SyntaxKind.ReadonlyKeyword) { if (node.type.kind !== SyntaxKind.ArrayType && node.type.kind !== SyntaxKind.TupleType) { return grammarErrorOnFirstToken(node, Diagnostics.readonly_type_modifier_is_only_permitted_on_array_and_tuple_literal_types, tokenToString(SyntaxKind.SymbolKeyword)); } } } function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) { if (isNonBindableDynamicName(node)) { return grammarErrorOnNode(node, message); } } function checkGrammarMethod(node: MethodDeclaration | MethodSignature) { if (checkGrammarFunctionLikeDeclaration(node)) { return true; } if (node.kind === SyntaxKind.MethodDeclaration) { if (node.parent.kind === SyntaxKind.ObjectLiteralExpression) { // We only disallow modifier on a method declaration if it is a property of object-literal-expression if (node.modifiers && !(node.modifiers.length === 1 && first(node.modifiers).kind === SyntaxKind.AsyncKeyword)) { return grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here); } else if (checkGrammarForInvalidQuestionMark(node.questionToken, Diagnostics.An_object_member_cannot_be_declared_optional)) { return true; } else if (checkGrammarForInvalidExclamationToken(node.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context)) { return true; } else if (node.body === undefined) { return grammarErrorAtPos(node, node.end - 1, ";".length, Diagnostics._0_expected, "{"); } } if (checkGrammarForGenerator(node)) { return true; } } if (isClassLike(node.parent)) { // Technically, computed properties in ambient contexts is disallowed // for property declarations and accessors too, not just methods. // However, property declarations disallow computed names in general, // and accessors are not allowed in ambient contexts in general, // so this error only really matters for methods. if (node.flags & NodeFlags.Ambient) { return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_ambient_context_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); } else if (node.kind === SyntaxKind.MethodDeclaration && !node.body) { return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_method_overload_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); } } else if (node.parent.kind === SyntaxKind.InterfaceDeclaration) { return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_interface_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); } else if (node.parent.kind === SyntaxKind.TypeLiteral) { return checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_type_literal_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type); } } function checkGrammarBreakOrContinueStatement(node: BreakOrContinueStatement): boolean { let current: Node = node; while (current) { if (isFunctionLike(current)) { return grammarErrorOnNode(node, Diagnostics.Jump_target_cannot_cross_function_boundary); } switch (current.kind) { case SyntaxKind.LabeledStatement: if (node.label && (current).label.escapedText === node.label.escapedText) { // found matching label - verify that label usage is correct // continue can only target labels that are on iteration statements const isMisplacedContinueLabel = node.kind === SyntaxKind.ContinueStatement && !isIterationStatement((current).statement, /*lookInLabeledStatement*/ true); if (isMisplacedContinueLabel) { return grammarErrorOnNode(node, Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement); } return false; } break; case SyntaxKind.SwitchStatement: if (node.kind === SyntaxKind.BreakStatement && !node.label) { // unlabeled break within switch statement - ok return false; } break; default: if (isIterationStatement(current, /*lookInLabeledStatement*/ false) && !node.label) { // unlabeled break or continue within iteration statement - ok return false; } break; } current = current.parent; } if (node.label) { const message = node.kind === SyntaxKind.BreakStatement ? Diagnostics.A_break_statement_can_only_jump_to_a_label_of_an_enclosing_statement : Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement; return grammarErrorOnNode(node, message); } else { const message = node.kind === SyntaxKind.BreakStatement ? Diagnostics.A_break_statement_can_only_be_used_within_an_enclosing_iteration_or_switch_statement : Diagnostics.A_continue_statement_can_only_be_used_within_an_enclosing_iteration_statement; return grammarErrorOnNode(node, message); } } function checkGrammarBindingElement(node: BindingElement) { if (node.dotDotDotToken) { const elements = node.parent.elements; if (node !== last(elements)) { return grammarErrorOnNode(node, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); } checkGrammarForDisallowedTrailingComma(elements, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); if (node.propertyName) { return grammarErrorOnNode(node.name, Diagnostics.A_rest_element_cannot_have_a_property_name); } } if (node.dotDotDotToken && node.initializer) { // Error on equals token which immediately precedes the initializer return grammarErrorAtPos(node, node.initializer.pos - 1, 1, Diagnostics.A_rest_element_cannot_have_an_initializer); } } function isStringOrNumberLiteralExpression(expr: Expression) { return isStringOrNumericLiteralLike(expr) || expr.kind === SyntaxKind.PrefixUnaryExpression && (expr).operator === SyntaxKind.MinusToken && (expr).operand.kind === SyntaxKind.NumericLiteral; } function isBigIntLiteralExpression(expr: Expression) { return expr.kind === SyntaxKind.BigIntLiteral || expr.kind === SyntaxKind.PrefixUnaryExpression && (expr).operator === SyntaxKind.MinusToken && (expr).operand.kind === SyntaxKind.BigIntLiteral; } function isSimpleLiteralEnumReference(expr: Expression) { if ((isPropertyAccessExpression(expr) || (isElementAccessExpression(expr) && isStringOrNumberLiteralExpression(expr.argumentExpression))) && isEntityNameExpression(expr.expression)) { return !!(checkExpressionCached(expr).flags & TypeFlags.EnumLiteral); } } function checkAmbientInitializer(node: VariableDeclaration | PropertyDeclaration | PropertySignature) { const {initializer} = node; if (initializer) { const isInvalidInitializer = !( isStringOrNumberLiteralExpression(initializer) || isSimpleLiteralEnumReference(initializer) || initializer.kind === SyntaxKind.TrueKeyword || initializer.kind === SyntaxKind.FalseKeyword || isBigIntLiteralExpression(initializer) ); const isConstOrReadonly = isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node); if (isConstOrReadonly && !node.type) { if (isInvalidInitializer) { return grammarErrorOnNode(initializer, Diagnostics.A_const_initializer_in_an_ambient_context_must_be_a_string_or_numeric_literal_or_literal_enum_reference); } } else { return grammarErrorOnNode(initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts); } if (!isConstOrReadonly || isInvalidInitializer) { return grammarErrorOnNode(initializer, Diagnostics.Initializers_are_not_allowed_in_ambient_contexts); } } } function checkGrammarVariableDeclaration(node: VariableDeclaration) { if (node.parent.parent.kind !== SyntaxKind.ForInStatement && node.parent.parent.kind !== SyntaxKind.ForOfStatement) { if (node.flags & NodeFlags.Ambient) { checkAmbientInitializer(node); } else if (!node.initializer) { if (isBindingPattern(node.name) && !isBindingPattern(node.parent)) { return grammarErrorOnNode(node, Diagnostics.A_destructuring_declaration_must_have_an_initializer); } if (isVarConst(node)) { return grammarErrorOnNode(node, Diagnostics.const_declarations_must_be_initialized); } } } if (node.exclamationToken && (node.parent.parent.kind !== SyntaxKind.VariableStatement || !node.type || node.initializer || node.flags & NodeFlags.Ambient)) { const message = node.initializer ? Diagnostics.Declarations_with_initializers_cannot_also_have_definite_assignment_assertions : Diagnostics.Declarations_with_definite_assignment_assertions_must_also_have_type_annotations; return grammarErrorOnNode(node.exclamationToken, message); } const moduleKind = getEmitModuleKind(compilerOptions); if (moduleKind < ModuleKind.ES2015 && moduleKind !== ModuleKind.System && !(node.parent.parent.flags & NodeFlags.Ambient) && hasSyntacticModifier(node.parent.parent, ModifierFlags.Export)) { checkESModuleMarker(node.name); } const checkLetConstNames = (isLet(node) || isVarConst(node)); // 1. LexicalDeclaration : LetOrConst BindingList ; // It is a Syntax Error if the BoundNames of BindingList contains "let". // 2. ForDeclaration: ForDeclaration : LetOrConst ForBinding // It is a Syntax Error if the BoundNames of ForDeclaration contains "let". // It is a SyntaxError if a VariableDeclaration or VariableDeclarationNoIn occurs within strict code // and its Identifier is eval or arguments return checkLetConstNames && checkGrammarNameInLetOrConstDeclarations(node.name); } function checkESModuleMarker(name: Identifier | BindingPattern): boolean { if (name.kind === SyntaxKind.Identifier) { if (idText(name) === "__esModule") { return grammarErrorOnNodeSkippedOn("noEmit", name, Diagnostics.Identifier_expected_esModule_is_reserved_as_an_exported_marker_when_transforming_ECMAScript_modules); } } else { const elements = name.elements; for (const element of elements) { if (!isOmittedExpression(element)) { return checkESModuleMarker(element.name); } } } return false; } function checkGrammarNameInLetOrConstDeclarations(name: Identifier | BindingPattern): boolean { if (name.kind === SyntaxKind.Identifier) { if (name.originalKeywordKind === SyntaxKind.LetKeyword) { return grammarErrorOnNode(name, Diagnostics.let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations); } } else { const elements = name.elements; for (const element of elements) { if (!isOmittedExpression(element)) { checkGrammarNameInLetOrConstDeclarations(element.name); } } } return false; } function checkGrammarVariableDeclarationList(declarationList: VariableDeclarationList): boolean { const declarations = declarationList.declarations; if (checkGrammarForDisallowedTrailingComma(declarationList.declarations)) { return true; } if (!declarationList.declarations.length) { return grammarErrorAtPos(declarationList, declarations.pos, declarations.end - declarations.pos, Diagnostics.Variable_declaration_list_cannot_be_empty); } return false; } function allowLetAndConstDeclarations(parent: Node): boolean { switch (parent.kind) { case SyntaxKind.IfStatement: case SyntaxKind.DoStatement: case SyntaxKind.WhileStatement: case SyntaxKind.WithStatement: case SyntaxKind.ForStatement: case SyntaxKind.ForInStatement: case SyntaxKind.ForOfStatement: return false; case SyntaxKind.LabeledStatement: return allowLetAndConstDeclarations(parent.parent); } return true; } function checkGrammarForDisallowedLetOrConstStatement(node: VariableStatement) { if (!allowLetAndConstDeclarations(node.parent)) { if (isLet(node.declarationList)) { return grammarErrorOnNode(node, Diagnostics.let_declarations_can_only_be_declared_inside_a_block); } else if (isVarConst(node.declarationList)) { return grammarErrorOnNode(node, Diagnostics.const_declarations_can_only_be_declared_inside_a_block); } } } function checkGrammarMetaProperty(node: MetaProperty) { const escapedText = node.name.escapedText; switch (node.keywordToken) { case SyntaxKind.NewKeyword: if (escapedText !== "target") { return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "target"); } break; case SyntaxKind.ImportKeyword: if (escapedText !== "meta") { return grammarErrorOnNode(node.name, Diagnostics._0_is_not_a_valid_meta_property_for_keyword_1_Did_you_mean_2, node.name.escapedText, tokenToString(node.keywordToken), "meta"); } break; } } function hasParseDiagnostics(sourceFile: SourceFile): boolean { return sourceFile.parseDiagnostics.length > 0; } function grammarErrorOnFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { const sourceFile = getSourceFileOfNode(node); if (!hasParseDiagnostics(sourceFile)) { const span = getSpanOfTokenAtPosition(sourceFile, node.pos); diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2)); return true; } return false; } function grammarErrorAtPos(nodeForSourceFile: Node, start: number, length: number, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { const sourceFile = getSourceFileOfNode(nodeForSourceFile); if (!hasParseDiagnostics(sourceFile)) { diagnostics.add(createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2)); return true; } return false; } function grammarErrorOnNodeSkippedOn(key: keyof CompilerOptions, node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { const sourceFile = getSourceFileOfNode(node); if (!hasParseDiagnostics(sourceFile)) { errorSkippedOn(key, node, message, arg0, arg1, arg2); return true; } return false; } function grammarErrorOnNode(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { const sourceFile = getSourceFileOfNode(node); if (!hasParseDiagnostics(sourceFile)) { diagnostics.add(createDiagnosticForNode(node, message, arg0, arg1, arg2)); return true; } return false; } function checkGrammarConstructorTypeParameters(node: ConstructorDeclaration) { const jsdocTypeParameters = isInJSFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined; const range = node.typeParameters || jsdocTypeParameters && firstOrUndefined(jsdocTypeParameters); if (range) { const pos = range.pos === range.end ? range.pos : skipTrivia(getSourceFileOfNode(node).text, range.pos); return grammarErrorAtPos(node, pos, range.end - pos, Diagnostics.Type_parameters_cannot_appear_on_a_constructor_declaration); } } function checkGrammarConstructorTypeAnnotation(node: ConstructorDeclaration) { const type = getEffectiveReturnTypeNode(node); if (type) { return grammarErrorOnNode(type, Diagnostics.Type_annotation_cannot_appear_on_a_constructor_declaration); } } function checkGrammarProperty(node: PropertyDeclaration | PropertySignature) { if (isClassLike(node.parent)) { if (isStringLiteral(node.name) && node.name.text === "constructor") { return grammarErrorOnNode(node.name, Diagnostics.Classes_may_not_have_a_field_named_constructor); } if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_class_property_declaration_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { return true; } if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(node.name)) { return grammarErrorOnNode(node.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); } } else if (node.parent.kind === SyntaxKind.InterfaceDeclaration) { if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_an_interface_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { return true; } if (node.initializer) { return grammarErrorOnNode(node.initializer, Diagnostics.An_interface_property_cannot_have_an_initializer); } } else if (node.parent.kind === SyntaxKind.TypeLiteral) { if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_type_literal_must_refer_to_an_expression_whose_type_is_a_literal_type_or_a_unique_symbol_type)) { return true; } if (node.initializer) { return grammarErrorOnNode(node.initializer, Diagnostics.A_type_literal_property_cannot_have_an_initializer); } } if (node.flags & NodeFlags.Ambient) { checkAmbientInitializer(node); } if (isPropertyDeclaration(node) && node.exclamationToken && (!isClassLike(node.parent) || !node.type || node.initializer || node.flags & NodeFlags.Ambient || hasSyntacticModifier(node, ModifierFlags.Static | ModifierFlags.Abstract))) { const message = node.initializer ? Diagnostics.Declarations_with_initializers_cannot_also_have_definite_assignment_assertions : !node.type ? Diagnostics.Declarations_with_definite_assignment_assertions_must_also_have_type_annotations : Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context; return grammarErrorOnNode(node.exclamationToken, message); } } function checkGrammarTopLevelElementForRequiredDeclareModifier(node: Node): boolean { // A declare modifier is required for any top level .d.ts declaration except export=, export default, export as namespace // interfaces and imports categories: // // DeclarationElement: // ExportAssignment // export_opt InterfaceDeclaration // export_opt TypeAliasDeclaration // export_opt ImportDeclaration // export_opt ExternalImportDeclaration // export_opt AmbientDeclaration // // TODO: The spec needs to be amended to reflect this grammar. if (node.kind === SyntaxKind.InterfaceDeclaration || node.kind === SyntaxKind.TypeAliasDeclaration || node.kind === SyntaxKind.ImportDeclaration || node.kind === SyntaxKind.ImportEqualsDeclaration || node.kind === SyntaxKind.ExportDeclaration || node.kind === SyntaxKind.ExportAssignment || node.kind === SyntaxKind.NamespaceExportDeclaration || hasSyntacticModifier(node, ModifierFlags.Ambient | ModifierFlags.Export | ModifierFlags.Default)) { return false; } return grammarErrorOnFirstToken(node, Diagnostics.Top_level_declarations_in_d_ts_files_must_start_with_either_a_declare_or_export_modifier); } function checkGrammarTopLevelElementsForRequiredDeclareModifier(file: SourceFile): boolean { for (const decl of file.statements) { if (isDeclaration(decl) || decl.kind === SyntaxKind.VariableStatement) { if (checkGrammarTopLevelElementForRequiredDeclareModifier(decl)) { return true; } } } return false; } function checkGrammarSourceFile(node: SourceFile): boolean { return !!(node.flags & NodeFlags.Ambient) && checkGrammarTopLevelElementsForRequiredDeclareModifier(node); } function checkGrammarStatementInAmbientContext(node: Node): boolean { if (node.flags & NodeFlags.Ambient) { // Find containing block which is either Block, ModuleBlock, SourceFile const links = getNodeLinks(node); if (!links.hasReportedStatementInAmbientContext && (isFunctionLike(node.parent) || isAccessor(node.parent))) { return getNodeLinks(node).hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts); } // We are either parented by another statement, or some sort of block. // If we're in a block, we only want to really report an error once // to prevent noisiness. So use a bit on the block to indicate if // this has already been reported, and don't report if it has. // if (node.parent.kind === SyntaxKind.Block || node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { const links = getNodeLinks(node.parent); // Check if the containing block ever report this error if (!links.hasReportedStatementInAmbientContext) { return links.hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.Statements_are_not_allowed_in_ambient_contexts); } } else { // We must be parented by a statement. If so, there's no need // to report the error as our parent will have already done it. // Debug.assert(isStatement(node.parent)); } } return false; } function checkGrammarNumericLiteral(node: NumericLiteral): boolean { // Grammar checking if (node.numericLiteralFlags & TokenFlags.Octal) { let diagnosticMessage: DiagnosticMessage | undefined; if (languageVersion >= ScriptTarget.ES5) { diagnosticMessage = Diagnostics.Octal_literals_are_not_available_when_targeting_ECMAScript_5_and_higher_Use_the_syntax_0; } else if (isChildOfNodeWithKind(node, SyntaxKind.LiteralType)) { diagnosticMessage = Diagnostics.Octal_literal_types_must_use_ES2015_syntax_Use_the_syntax_0; } else if (isChildOfNodeWithKind(node, SyntaxKind.EnumMember)) { diagnosticMessage = Diagnostics.Octal_literals_are_not_allowed_in_enums_members_initializer_Use_the_syntax_0; } if (diagnosticMessage) { const withMinus = isPrefixUnaryExpression(node.parent) && node.parent.operator === SyntaxKind.MinusToken; const literal = (withMinus ? "-" : "") + "0o" + node.text; return grammarErrorOnNode(withMinus ? node.parent : node, diagnosticMessage, literal); } } // Realism (size) checking checkNumericLiteralValueSize(node); return false; } function checkNumericLiteralValueSize(node: NumericLiteral) { // Scientific notation (e.g. 2e54 and 1e00000000010) can't be converted to bigint // Literals with 15 or fewer characters aren't long enough to reach past 2^53 - 1 // Fractional numbers (e.g. 9000000000000000.001) are inherently imprecise anyway if (node.numericLiteralFlags & TokenFlags.Scientific || node.text.length <= 15 || node.text.indexOf(".") !== -1) { return; } // We can't rely on the runtime to accurately store and compare extremely large numeric values // Even for internal use, we use getTextOfNode: https://github.com/microsoft/TypeScript/issues/33298 // Thus, if the runtime claims a too-large number is lower than Number.MAX_SAFE_INTEGER, // it's likely addition operations on it will fail too const apparentValue = +getTextOfNode(node); if (apparentValue <= 2 ** 53 - 1 && apparentValue + 1 > apparentValue) { return; } addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.Numeric_literals_with_absolute_values_equal_to_2_53_or_greater_are_too_large_to_be_represented_accurately_as_integers)); } function checkGrammarBigIntLiteral(node: BigIntLiteral): boolean { const literalType = isLiteralTypeNode(node.parent) || isPrefixUnaryExpression(node.parent) && isLiteralTypeNode(node.parent.parent); if (!literalType) { if (languageVersion < ScriptTarget.ES2020) { if (grammarErrorOnNode(node, Diagnostics.BigInt_literals_are_not_available_when_targeting_lower_than_ES2020)) { return true; } } } return false; } function grammarErrorAfterFirstToken(node: Node, message: DiagnosticMessage, arg0?: any, arg1?: any, arg2?: any): boolean { const sourceFile = getSourceFileOfNode(node); if (!hasParseDiagnostics(sourceFile)) { const span = getSpanOfTokenAtPosition(sourceFile, node.pos); diagnostics.add(createFileDiagnostic(sourceFile, textSpanEnd(span), /*length*/ 0, message, arg0, arg1, arg2)); return true; } return false; } function getAmbientModules(): Symbol[] { if (!ambientModulesCache) { ambientModulesCache = []; globals.forEach((global, sym) => { // No need to `unescapeLeadingUnderscores`, an escaped symbol is never an ambient module. if (ambientModuleSymbolRegex.test(sym as string)) { ambientModulesCache!.push(global); } }); } return ambientModulesCache; } function checkGrammarImportClause(node: ImportClause): boolean { if (node.isTypeOnly && node.name && node.namedBindings) { return grammarErrorOnNode(node, Diagnostics.A_type_only_import_can_specify_a_default_import_or_named_bindings_but_not_both); } return false; } function checkGrammarImportCallExpression(node: ImportCall): boolean { if (moduleKind === ModuleKind.ES2015) { return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_es2020_esnext_commonjs_amd_system_or_umd); } if (node.typeArguments) { return grammarErrorOnNode(node, Diagnostics.Dynamic_import_cannot_have_type_arguments); } const nodeArguments = node.arguments; if (nodeArguments.length !== 1) { return grammarErrorOnNode(node, Diagnostics.Dynamic_import_must_have_one_specifier_as_an_argument); } checkGrammarForDisallowedTrailingComma(nodeArguments); // see: parseArgumentOrArrayLiteralElement...we use this function which parse arguments of callExpression to parse specifier for dynamic import. // parseArgumentOrArrayLiteralElement allows spread element to be in an argument list which is not allowed as specifier in dynamic import. if (isSpreadElement(nodeArguments[0])) { return grammarErrorOnNode(nodeArguments[0], Diagnostics.Specifier_of_dynamic_import_cannot_be_spread_element); } return false; } function findMatchingTypeReferenceOrTypeAliasReference(source: Type, unionTarget: UnionOrIntersectionType) { const sourceObjectFlags = getObjectFlags(source); if (sourceObjectFlags & (ObjectFlags.Reference | ObjectFlags.Anonymous) && unionTarget.flags & TypeFlags.Union) { return find(unionTarget.types, target => { if (target.flags & TypeFlags.Object) { const overlapObjFlags = sourceObjectFlags & getObjectFlags(target); if (overlapObjFlags & ObjectFlags.Reference) { return (source as TypeReference).target === (target as TypeReference).target; } if (overlapObjFlags & ObjectFlags.Anonymous) { return !!(source as AnonymousType).aliasSymbol && (source as AnonymousType).aliasSymbol === (target as AnonymousType).aliasSymbol; } } return false; }); } } function findBestTypeForObjectLiteral(source: Type, unionTarget: UnionOrIntersectionType) { if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && forEachType(unionTarget, isArrayLikeType)) { return find(unionTarget.types, t => !isArrayLikeType(t)); } } function findBestTypeForInvokable(source: Type, unionTarget: UnionOrIntersectionType) { let signatureKind = SignatureKind.Call; const hasSignatures = getSignaturesOfType(source, signatureKind).length > 0 || (signatureKind = SignatureKind.Construct, getSignaturesOfType(source, signatureKind).length > 0); if (hasSignatures) { return find(unionTarget.types, t => getSignaturesOfType(t, signatureKind).length > 0); } } function findMostOverlappyType(source: Type, unionTarget: UnionOrIntersectionType) { let bestMatch: Type | undefined; let matchingCount = 0; for (const target of unionTarget.types) { const overlap = getIntersectionType([getIndexType(source), getIndexType(target)]); if (overlap.flags & TypeFlags.Index) { // perfect overlap of keys bestMatch = target; matchingCount = Infinity; } else if (overlap.flags & TypeFlags.Union) { // We only want to account for literal types otherwise. // If we have a union of index types, it seems likely that we // needed to elaborate between two generic mapped types anyway. const len = length(filter((overlap as UnionType).types, isUnitType)); if (len >= matchingCount) { bestMatch = target; matchingCount = len; } } else if (isUnitType(overlap) && 1 >= matchingCount) { bestMatch = target; matchingCount = 1; } } return bestMatch; } function filterPrimitivesIfContainsNonPrimitive(type: UnionType) { if (maybeTypeOfKind(type, TypeFlags.NonPrimitive)) { const result = filterType(type, t => !(t.flags & TypeFlags.Primitive)); if (!(result.flags & TypeFlags.Never)) { return result; } } return type; } // Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly function findMatchingDiscriminantType(source: Type, target: Type, isRelatedTo: (source: Type, target: Type) => Ternary, skipPartial?: boolean) { if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) { const sourceProperties = getPropertiesOfType(source); if (sourceProperties) { const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); if (sourcePropertiesFiltered) { return discriminateTypeByDiscriminableItems(target, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo, /*defaultValue*/ undefined, skipPartial); } } } return undefined; } } function isNotAccessor(declaration: Declaration): boolean { // Accessors check for their own matching duplicates, and in contexts where they are valid, there are already duplicate identifier checks return !isAccessor(declaration); } function isNotOverload(declaration: Declaration): boolean { return (declaration.kind !== SyntaxKind.FunctionDeclaration && declaration.kind !== SyntaxKind.MethodDeclaration) || !!(declaration as FunctionDeclaration).body; } /** Like 'isDeclarationName', but returns true for LHS of `import { x as y }` or `export { x as y }`. */ function isDeclarationNameOrImportPropertyName(name: Node): boolean { switch (name.parent.kind) { case SyntaxKind.ImportSpecifier: case SyntaxKind.ExportSpecifier: return isIdentifier(name); default: return isDeclarationName(name); } } function isSomeImportDeclaration(decl: Node): boolean { switch (decl.kind) { case SyntaxKind.ImportClause: // For default import case SyntaxKind.ImportEqualsDeclaration: case SyntaxKind.NamespaceImport: case SyntaxKind.ImportSpecifier: // For rename import `x as y` return true; case SyntaxKind.Identifier: // For regular import, `decl` is an Identifier under the ImportSpecifier. return decl.parent.kind === SyntaxKind.ImportSpecifier; default: return false; } } namespace JsxNames { export const JSX = "JSX" as __String; export const IntrinsicElements = "IntrinsicElements" as __String; export const ElementClass = "ElementClass" as __String; export const ElementAttributesPropertyNameContainer = "ElementAttributesProperty" as __String; // TODO: Deprecate and remove support export const ElementChildrenAttributeNameContainer = "ElementChildrenAttribute" as __String; export const Element = "Element" as __String; export const IntrinsicAttributes = "IntrinsicAttributes" as __String; export const IntrinsicClassAttributes = "IntrinsicClassAttributes" as __String; export const LibraryManagedAttributes = "LibraryManagedAttributes" as __String; } function getIterationTypesKeyFromIterationTypeKind(typeKind: IterationTypeKind) { switch (typeKind) { case IterationTypeKind.Yield: return "yieldType"; case IterationTypeKind.Return: return "returnType"; case IterationTypeKind.Next: return "nextType"; } } export function signatureHasRestParameter(s: Signature) { return !!(s.flags & SignatureFlags.HasRestParameter); } export function signatureHasLiteralTypes(s: Signature) { return !!(s.flags & SignatureFlags.HasLiteralTypes); } }