From f9572f5c09c4f426144acef4a0a6eeaaae648172 Mon Sep 17 00:00:00 2001 From: Peter Krol Date: Mon, 24 Apr 2023 23:25:49 -0400 Subject: [PATCH] fix: findReferences --- src/compiler/checker.ts | 34070 ++++++++++++++-------------- src/compiler/types.ts | 1280 +- src/services/findAllReferences.ts | 28 +- 3 files changed, 17743 insertions(+), 17635 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b95f8cf13a..5bf250cfd6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1114,11 +1114,14 @@ import { VariableDeclarationWithFunctionType, VariableDeclarationWithIdentifier, LocalsContainer, + TsPlusTypeChecker, } from "./_namespaces/ts"; import * as moduleSpecifiers from "./_namespaces/ts.moduleSpecifiers"; import * as performance from "./_namespaces/ts.performance"; -// TSPLUS EXTENSION START +// +// TSPLUS START +// const tsPlusDebug = false; const invertedBinaryOp = { [SyntaxKind.LessThanToken]: "<" as __String, @@ -1145,7 +1148,9 @@ const invertedBinaryOp = { [SyntaxKind.BarBarToken]: "||" as __String, [SyntaxKind.QuestionQuestionToken]: "??" as __String } as const; -// TSPLUS EXTENSION END +// +// TSPLUS END +// const ambientModuleSymbolRegex = /^".+"$/; const anon = "(anonymous)" as __String & string; @@ -1483,7 +1488,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { deferredDiagnosticsCallbacks.push(arg); }; - // TSPLUS EXTENSION START + // + // TSPLUS START + // const typeHashCache = new Map(); const unresolvedTypeDeclarations = new Set() const unresolvedCompanionDeclarations = new Set() @@ -1519,7 +1526,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const tsPlusGlobalImportCache = new Map() const tsPlusFiles = new Map>(); const tsPlusFilesFinal = new Map>(); - // TSPLUS EXTENSION END + // + // TSPLUS END + // // 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 @@ -1603,6 +1612,132 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { /** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */ var apparentArgumentCount: number | undefined; + // TSPLUS START + const tsPlusChecker: TsPlusTypeChecker = { + getExtensions, + getGetterExtension, + getFluentExtension, + getStaticExtension, + getStaticCompanionExtension, + getGetterCompanionExtension, + getCallExtension, + isPipeCall: (node) => { + const type = getTypeOfNode(node.expression); + if (type.symbol && type.symbol.declarations) { + return type.symbol.declarations.flatMap(collectTsPlusMacroTags).findIndex((tag) => tag === "pipe") !== -1; + } + return false; + }, + isTailRec, + cloneSymbol, + getTextOfBinaryOp, + getInstantiatedTsPlusSignature, + getIndexAccessExpressionCache: () => indexAccessExpressionCache, + isTsPlusMacroCall, + isTsPlusMacroGetter, + isCompanionReference, + collectTsPlusFluentTags, + hasExportedPlusTags: (declaration) => { + return collectTsPlusFluentTags(declaration).length > 0 || + collectTsPlusPipeableTags(declaration).length > 0 || + collectTsPlusGetterTags(declaration).length > 0 || + collectTsPlusIndexTags(declaration).length > 0 || + collectTsPlusPipeableIndexTags(declaration).length > 0 || + collectTsPlusOperatorTags(declaration).length > 0 || + collectTsPlusPipeableOperatorTags(declaration).length > 0 || + collectTsPlusStaticTags(declaration).length > 0 || + isTsPlusImplicit(declaration) || + collectTsPlusDeriveTags(declaration).length > 0 + }, + getFluentExtensionForPipeableSymbol, + getPrimitiveTypeName, + getResolvedOperator: (node) => { + const nodeLinks = getNodeLinks(node.operatorToken); + if (nodeLinks.resolvedSignature === undefined && nodeLinks.isTsPlusOperatorToken === undefined) { + checkBinaryLikeExpression(node.left, node.operatorToken, node.right); + } + return nodeLinks.resolvedSignature; + }, + getNodeLinks, + collectTsPlusMacroTags, + getTsPlusGlobals: () => { + return arrayFrom(mapIterator(tsPlusGlobalImportCache.values(), ({ importSpecifier }) => getSymbolAtLocation(importSpecifier.name)!)); + }, + getTsPlusGlobal: (name) => { + return tsPlusGlobalImportCache.get(name); + }, + findAndCheckDoAncestor: (node) => { + const doCall = findAncestor(node, (node): node is CallExpression => { + if (isCallExpression(node) && isIdentifier(node.expression)) { + const symbol = checker.getSymbolAtLocation(node.expression) + if (symbol && symbol.declarations) { + for (const declaration of symbol.declarations) { + return collectTsPlusMacroTags(declaration).indexOf("Do") !== -1; + } + } + } + return false; + }) + if (doCall) { + checker.getTypeAtLocation(doCall); + } + }, + getTsPlusSymbolAtLocation: (node) => { + const type = checker.getTypeAtLocation(node); + let symbol: TsPlusSymbol | undefined; + if (isTsPlusType(type)) { + symbol = type.tsPlusSymbol; + } + if (type.symbol && isTsPlusSymbol(type.symbol)) { + symbol = type.symbol; + } + if (type.aliasSymbol && isTsPlusSymbol(type.aliasSymbol)) { + symbol = type.aliasSymbol; + } + if (!symbol) { + symbol = getNodeLinks(node.parent).tsPlusSymbol; + } + return symbol; + }, + getTsPlusExtensionsAtLocation: (node, includeDeclaration) => { + const symbol = checker.getTsPlusSymbolAtLocation(node); + if (symbol) { + switch (symbol.tsPlusTag) { + case TsPlusSymbolTag.Fluent: { + const signature = symbol.tsPlusResolvedSignatures[0]; + if (signature && signature.tsPlusDeclaration) { + return checker.getExtensionsForDeclaration(signature.tsPlusDeclaration); + } + break; + } + case TsPlusSymbolTag.Getter: + case TsPlusSymbolTag.GetterVariable: + case TsPlusSymbolTag.StaticValue: + case TsPlusSymbolTag.StaticFunction: { + return checker.getExtensionsForDeclaration(symbol.tsPlusDeclaration); + } + } + } else { + if (isDeclaration(node.parent) && includeDeclaration) { + return checker.getExtensionsForDeclaration(node.parent) + } + } + return []; + }, + getExtensionsForDeclaration: (node) => { + return collectTsPlusStaticTags(node) + .concat(collectTsPlusFluentTags(node)) + .concat(collectTsPlusPipeableTags(node)) + .concat(collectTsPlusGetterTags(node)) + .concat(collectTsPlusOperatorTags(node)) + .concat(collectTsPlusPipeableOperatorTags(node)) + }, + getTsPlusFiles: () => tsPlusFilesFinal, + getTsPlusGlobalImports: () => tsPlusGlobalImportCache, + getSymbolLinks, + } + // TSPLUS END + // 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 @@ -1954,125 +2089,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getMemberOverrideModifierStatus, isTypeParameterPossiblyReferenced, typeHasCallOrConstructSignatures, - // TSPLUS EXTENSION BEGIN - getExtensions, - getGetterExtension, - getFluentExtension, - getStaticExtension, - getStaticCompanionExtension, - getGetterCompanionExtension, - getCallExtension, - isPipeCall: (node) => { - const type = getTypeOfNode(node.expression); - if (type.symbol && type.symbol.declarations) { - return type.symbol.declarations.flatMap(collectTsPlusMacroTags).findIndex((tag) => tag === "pipe") !== -1; - } - return false; - }, - isTailRec, - cloneSymbol, - getTextOfBinaryOp, - getInstantiatedTsPlusSignature, - getIndexAccessExpressionCache: () => indexAccessExpressionCache, - isTsPlusMacroCall, - isTsPlusMacroGetter, - isCompanionReference, - collectTsPlusFluentTags, - hasExportedPlusTags: (declaration) => { - return collectTsPlusFluentTags(declaration).length > 0 || - collectTsPlusPipeableTags(declaration).length > 0 || - collectTsPlusGetterTags(declaration).length > 0 || - collectTsPlusIndexTags(declaration).length > 0 || - collectTsPlusPipeableIndexTags(declaration).length > 0 || - collectTsPlusOperatorTags(declaration).length > 0 || - collectTsPlusPipeableOperatorTags(declaration).length > 0 || - collectTsPlusStaticTags(declaration).length > 0 || - isTsPlusImplicit(declaration) || - collectTsPlusDeriveTags(declaration).length > 0 - }, - getFluentExtensionForPipeableSymbol, - getPrimitiveTypeName, - getResolvedOperator: (node) => { - const nodeLinks = getNodeLinks(node.operatorToken); - if (nodeLinks.resolvedSignature === undefined && nodeLinks.isTsPlusOperatorToken === undefined) { - checkBinaryLikeExpression(node.left, node.operatorToken, node.right); - } - return nodeLinks.resolvedSignature; - }, - getNodeLinks, - collectTsPlusMacroTags, - getTsPlusGlobals: () => { - return arrayFrom(mapIterator(tsPlusGlobalImportCache.values(), ({ importSpecifier }) => getSymbolAtLocation(importSpecifier.name)!)); - }, - getTsPlusGlobal: (name) => { - return tsPlusGlobalImportCache.get(name); - }, - findAndCheckDoAncestor: (node) => { - const doCall = findAncestor(node, (node): node is CallExpression => { - if (isCallExpression(node) && isIdentifier(node.expression)) { - const symbol = checker.getSymbolAtLocation(node.expression) - if (symbol && symbol.declarations) { - for (const declaration of symbol.declarations) { - return collectTsPlusMacroTags(declaration).indexOf("Do") !== -1; - } - } - } - return false; - }) - if (doCall) { - checker.getTypeAtLocation(doCall); - } - }, - getTsPlusSymbolAtLocation: (node) => { - const type = checker.getTypeAtLocation(node); - let symbol: TsPlusSymbol | undefined; - if (isTsPlusType(type)) { - symbol = type.tsPlusSymbol; - } - if (type.symbol && isTsPlusSymbol(type.symbol)) { - symbol = type.symbol; - } - if (type.aliasSymbol && isTsPlusSymbol(type.aliasSymbol)) { - symbol = type.aliasSymbol; - } - if (!symbol) { - symbol = getNodeLinks(node.parent).tsPlusSymbol; - } - return symbol; - }, - getTsPlusExtensionsAtLocation: (node) => { - const symbol = checker.getTsPlusSymbolAtLocation(node); - if (symbol) { - switch (symbol.tsPlusTag) { - case TsPlusSymbolTag.Fluent: { - const signature = symbol.tsPlusResolvedSignatures[0]; - if (signature && signature.tsPlusDeclaration) { - return checker.getExtensionsForDeclaration(signature.tsPlusDeclaration); - } - break; - } - case TsPlusSymbolTag.Getter: - case TsPlusSymbolTag.GetterVariable: - case TsPlusSymbolTag.StaticValue: - case TsPlusSymbolTag.StaticFunction: { - return checker.getExtensionsForDeclaration(symbol.tsPlusDeclaration); - } - } - } - return []; - }, - getExtensionsForDeclaration: (node) => { - return collectTsPlusStaticTags(node) - .concat(collectTsPlusFluentTags(node)) - .concat(collectTsPlusPipeableTags(node)) - .concat(collectTsPlusGetterTags(node)) - .concat(collectTsPlusOperatorTags(node)) - .concat(collectTsPlusPipeableOperatorTags(node)) - }, - getTsPlusFiles: () => tsPlusFilesFinal, - getTsPlusGlobalImports: () => tsPlusGlobalImportCache, - getSymbolLinks, - // TSPLUS EXTENSION END + ...tsPlusChecker }; function runWithoutResolvedSignatureCaching(node: Node | undefined, fn: () => T): T { @@ -2112,1831 +2129,1139 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } - // TSPLUS EXTENSION BEGIN - function getPrimitiveTypeName(type: Type): string | undefined { - if (type.flags & TypeFlags.StringLike) { - return "String"; - } - if (type.flags & TypeFlags.NumberLike) { - return "Number"; - } - if (type.flags & TypeFlags.BooleanLike) { - return "Boolean"; - } - if (type.flags & TypeFlags.BigIntLike) { - return "BigInt"; - } - } - function getTextOfBinaryOp(kind: SyntaxKind): string | undefined { - return invertedBinaryOp[kind as keyof typeof invertedBinaryOp] as string | undefined; - } - function getCallExtension(node: Node) { - return callCache.get(node); - } - function isTailRec(node: Node) { - const links = getNodeLinks(node); - if (links.isTsPlusTailRec === undefined) { - for (const tag of getJSDocTags(node)) { - if (tag.tagName.escapedText === "tsplus" && typeof tag.comment === "string" && tag.comment === "tailRec") { - links.isTsPlusTailRec = true; - return links.isTsPlusTailRec; - } - } - links.isTsPlusTailRec = false; - } - return links.isTsPlusTailRec; - } - function getFluentExtensionForPipeableSymbol(symbol: TsPlusPipeableIdentifierSymbol) { - const extension = fluentCache.get(symbol.tsPlusTypeName)?.get(symbol.tsPlusName)?.(); - if (extension && every(extension.signatures, (sig) => !sig.tsPlusPipeable)) { - return extension; - } + function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined { + const node = getParseTreeNode(nodeIn, isCallLikeExpression); + apparentArgumentCount = argumentCount; + const res = !node ? undefined : getResolvedSignature(node, candidatesOutArray, checkMode); + apparentArgumentCount = undefined; + return res; } - function intersectSets(sets: readonly Set[]): Set { - if (sets.length === 0) { - return new Set(); - } - if (sets.length === 1) { - return sets[0]; - } - let shortest: Set | undefined; - for (const set of sets) { - if (shortest === undefined || shortest.size > set.size) { - shortest = set - } - } + var tupleTypes = new Map(); + var unionTypes = new Map(); + var unionOfUnionTypes = new Map(); + var intersectionTypes = new Map(); + var stringLiteralTypes = new Map(); + var numberLiteralTypes = new Map(); + var bigIntLiteralTypes = new Map(); + var enumLiteralTypes = new Map(); + var indexedAccessTypes = new Map(); + var templateLiteralTypes = new Map(); + var stringMappingTypes = new Map(); + var substitutionTypes = new Map(); + var subtypeReductionCache = new Map(); + var decoratorContextOverrideTypeCache = new Map(); + var cachedTypes = new Map(); + var evolvingArrayTypes: EvolvingArrayType[] = []; + var undefinedProperties: SymbolTable = new Map(); + var markerTypes = new Set(); - // copy the shortest set, so as not to iterate over and delete items from the same set - const out = new Set(shortest) + var unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String); + var resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving); + var unresolvedSymbols = new Map(); + var errorTypes = new Map(); - for (const set of sets) { - shortest!.forEach((a) => { - if (!set.has(a)) { - out.delete(a) - } - }) - } + // We specifically create the `undefined` and `null` types before any other types that can occur in + // unions such that they are given low type IDs and occur first in the sorted list of union constituents. + // We can then just examine the first constituent(s) of a union to check for their presence. - return out; - } - function collectRelevantSymbolsLoop(originalTarget: Type, lastNoInherit: Set, lastSeen?: Set) { - const seen: Set = new Set(lastSeen); - const noInherit: Set = new Set(lastNoInherit); - const relevant: Set = new Set(); - let queue: Type[] = [originalTarget] - while (queue.length > 0) { - const target = queue.shift()! - if (target.symbol) { - collectExcludedInheritance(target.symbol); - } - if (target.aliasSymbol) { - collectExcludedInheritance(target.aliasSymbol); - } - if (target.symbol && shouldInherit(target.symbol)) { - // Add the current symbol to the return Set - relevant.add(target.symbol) - // Check if the current type inherits other types - if (inheritanceSymbolCache.has(target.symbol)) { - inheritanceSymbolCache.get(target.symbol)!.forEach(addInheritedSymbol); - } - else if (target.symbol.declarations) { - target.symbol.declarations.forEach((declaration) => { - if ((isInterfaceDeclaration(declaration) || isClassDeclaration(declaration)) && declaration.heritageClauses) { - tryCacheTsPlusInheritance(target.symbol, declaration.heritageClauses); - if (inheritanceSymbolCache.has(target.symbol)) { - inheritanceSymbolCache.get(target.symbol)!.forEach(addInheritedSymbol); - } - } - }) - } - } - if (target.aliasSymbol && shouldInherit(target.aliasSymbol)) { - // Add the current symbol to the return Set - relevant.add(target.aliasSymbol); - if (inheritanceSymbolCache.has(target.aliasSymbol)) { - inheritanceSymbolCache.get(target.aliasSymbol)!.forEach(addInheritedSymbol) - } - // If the current type is a union type, add the inherited type symbols common to all members - if (target.flags & TypeFlags.Union) { - collectUnionType(target as UnionType); - } - // If the current type is an intersection type, simply enqueue all unseen members - if (target.flags & TypeFlags.Intersection) { - collectIntersectionType(target as IntersectionType) - } - } - if (!target.symbol && !target.aliasSymbol) { - if (target.flags & TypeFlags.Union) { - collectUnionType(target as UnionType); - } - if (target.flags & TypeFlags.Intersection) { - collectIntersectionType(target as IntersectionType) - } - } + var anyType = createIntrinsicType(TypeFlags.Any, "any"); + var autoType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.NonInferrableType); + var wildcardType = createIntrinsicType(TypeFlags.Any, "any"); + var errorType = createIntrinsicType(TypeFlags.Any, "error"); + var unresolvedType = createIntrinsicType(TypeFlags.Any, "unresolved"); + var nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType); + var intrinsicMarkerType = createIntrinsicType(TypeFlags.Any, "intrinsic"); + var unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); + var nonNullUnknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); + var undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); + var undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType); + var missingType = createIntrinsicType(TypeFlags.Undefined, "undefined"); + var undefinedOrMissingType = exactOptionalPropertyTypes ? missingType : undefinedType; + var optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined"); + var nullType = createIntrinsicType(TypeFlags.Null, "null"); + var nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType); + var stringType = createIntrinsicType(TypeFlags.String, "string"); + var numberType = createIntrinsicType(TypeFlags.Number, "number"); + var bigintType = createIntrinsicType(TypeFlags.BigInt, "bigint"); + var falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; + var regularFalseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; + var trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType; + var regularTrueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType; + trueType.regularType = regularTrueType; + trueType.freshType = trueType; + regularTrueType.regularType = regularTrueType; + regularTrueType.freshType = trueType; + falseType.regularType = regularFalseType; + falseType.freshType = falseType; + regularFalseType.regularType = regularFalseType; + regularFalseType.freshType = falseType; + var booleanType = getUnionType([regularFalseType, regularTrueType]); + var esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); + var voidType = createIntrinsicType(TypeFlags.Void, "void"); + var neverType = createIntrinsicType(TypeFlags.Never, "never"); + var silentNeverType = createIntrinsicType(TypeFlags.Never, "never", ObjectFlags.NonInferrableType); + var implicitNeverType = createIntrinsicType(TypeFlags.Never, "never"); + var unreachableNeverType = createIntrinsicType(TypeFlags.Never, "never"); + var nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object"); + var stringOrNumberType = getUnionType([stringType, numberType]); + var stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]); + var keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType; + var numberOrBigIntType = getUnionType([numberType, bigintType]); + var templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType, nullType, undefinedType]) as UnionType; + var numericStringType = getTemplateLiteralType(["", ""], [numberType]); // The `${number}` type + + var restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(t as TypeParameter) : t, () => "(restrictive mapper)"); + var permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t, () => "(permissive mapper)"); + var uniqueLiteralType = createIntrinsicType(TypeFlags.Never, "never"); // `uniqueLiteralType` is a special `never` flagged by union reduction to behave as a literal + var uniqueLiteralMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? uniqueLiteralType : t, () => "(unique literal mapper)"); // replace all type parameters with the unique literal type (disregarding constraints) + var outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined; + var reportUnreliableMapper = makeFunctionTypeMapper(t => { + if (outofbandVarianceMarkerHandler && (t === markerSuperType || t === markerSubType || t === markerOtherType)) { + outofbandVarianceMarkerHandler(/*onlyUnreliable*/ true); } - seen.clear(); - return relevant; + return t; + }, () => "(unmeasurable reporter)"); + var reportUnmeasurableMapper = makeFunctionTypeMapper(t => { + if (outofbandVarianceMarkerHandler && (t === markerSuperType || t === markerSubType || t === markerOtherType)) { + outofbandVarianceMarkerHandler(/*onlyUnreliable*/ false); + } + return t; + }, () => "(unreliable reporter)"); - function addInheritedSymbol(symbol: Symbol) { - if (shouldInherit(symbol) && symbol.declarations && symbol.declarations.length > 0) { - for (const decl of symbol.declarations) { - // Exclude all declarations but interface and class declarations. - // Symbol declarations can also include other declarations, such as variable declarations - // and module declarations, which we do not want to include for inheritance - if (isInterfaceDeclaration(decl) || isClassDeclaration(decl) || isTypeAliasDeclaration(decl)) { - const type = getTypeOfNode(decl); - if (seen.has(type)) { - continue; - } + var emptyObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + var emptyJsxObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + emptyJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes; - if (!isErrorType(type)) { - seen.add(type); - queue.push(type); - } - } - } - } - } + var emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); + emptyTypeLiteralSymbol.members = createSymbolTable(); + var emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, emptyArray); - function collectUnionType(type: UnionType) { - const types = (type as UnionType).types; - const inherited: Set[] = [] - for (const member of types) { - if (!seen.has(member)) { - inherited.push(collectRelevantSymbolsLoop(member, noInherit, seen)); - } - } - // Add union members as "seen" only after the union has been collected - for (const member of types) { - seen.add(member); - } + var unknownEmptyObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + var unknownUnionType = strictNullChecks ? getUnionType([undefinedType, nullType, unknownEmptyObjectType]) : unknownType; - intersectSets(inherited).forEach((s) => { - shouldInherit(s) && relevant.add(s) - }) - } + var emptyGenericType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray) as ObjectType as GenericType; + emptyGenericType.instantiations = new Map(); - function collectIntersectionType(type: IntersectionType) { - for (const member of type.types) { - if (!seen.has(member)) { - seen.add(member); - queue.push(member); - } - } - } + var anyFunctionType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated + // in getPropagatingFlagsOfTypes, and it is checked in inferFromTypes. + anyFunctionType.objectFlags |= ObjectFlags.NonInferrableType; - function collectExcludedInheritance(symbol: Symbol) { - if (symbol.declarations) { - symbol.declarations.forEach((declaration) => { - if ((isInterfaceDeclaration(declaration) || isTypeAliasDeclaration(declaration) || isClassDeclaration(declaration)) && - declaration.tsPlusNoInheritTags) { - declaration.tsPlusNoInheritTags.forEach((tag) => { - noInherit.add(tag) - }) + var noConstraintType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + var circularConstraintType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + var resolvingDefaultType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + + var markerSuperType = createTypeParameter(); + var markerSubType = createTypeParameter(); + markerSubType.constraint = markerSuperType; + var markerOtherType = createTypeParameter(); + + var markerSuperTypeForCheck = createTypeParameter(); + var markerSubTypeForCheck = createTypeParameter(); + markerSubTypeForCheck.constraint = markerSuperTypeForCheck; + + var noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<>", 0, anyType); + + var anySignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + var unknownSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + var resolvingSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + var silentNeverSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + + var enumNumberIndexInfo = createIndexInfo(numberType, stringType, /*isReadonly*/ true); + + var iterationTypesCache = new Map(); // cache for common IterationTypes instances + var noIterationTypes: IterationTypes = { + get yieldType(): Type { return Debug.fail("Not supported"); }, + get returnType(): Type { return Debug.fail("Not supported"); }, + get nextType(): Type { return Debug.fail("Not supported"); }, + }; + + var anyIterationTypes = createIterationTypes(anyType, anyType, anyType); + var anyIterationTypesExceptNext = createIterationTypes(anyType, anyType, unknownType); + var defaultIterationTypes = createIterationTypes(neverType, anyType, undefinedType); // default iteration types for `Iterator`. + + var 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, + }; + + var 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: Map; + } + /** Key is "/path/to/a.ts|/path/to/b.ts". */ + var amalgamatedDuplicates: Map | undefined; + var reverseMappedCache = new Map(); + var inInferTypeForHomomorphicMappedType = false; + var ambientModulesCache: Symbol[] | undefined; + /** + * List of every ambient module with a "*" wildcard. + * Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches. + * This is only used if there is no exact match. + */ + var patternAmbientModules: PatternAmbientModule[]; + var patternAmbientModuleAugmentations: Map | undefined; + + var globalObjectType: ObjectType; + var globalFunctionType: ObjectType; + var globalCallableFunctionType: ObjectType; + var globalNewableFunctionType: ObjectType; + var globalArrayType: GenericType; + var globalReadonlyArrayType: GenericType; + var globalStringType: ObjectType; + var globalNumberType: ObjectType; + var globalBooleanType: ObjectType; + var globalRegExpType: ObjectType; + var globalThisType: GenericType; + var anyArrayType: Type; + var autoArrayType: Type; + var anyReadonlyArrayType: Type; + var deferredGlobalNonNullableTypeAlias: Symbol; + + // The library files are only loaded when the feature is used. + // This allows users to just specify library files they want to used through --lib + // and they will not get an error from not having unrelated library files + var deferredGlobalESSymbolConstructorSymbol: Symbol | undefined; + var deferredGlobalESSymbolConstructorTypeSymbol: Symbol | undefined; + var deferredGlobalESSymbolType: ObjectType | undefined; + var deferredGlobalTypedPropertyDescriptorType: GenericType; + var deferredGlobalPromiseType: GenericType | undefined; + var deferredGlobalPromiseLikeType: GenericType | undefined; + var deferredGlobalPromiseConstructorSymbol: Symbol | undefined; + var deferredGlobalPromiseConstructorLikeType: ObjectType | undefined; + var deferredGlobalIterableType: GenericType | undefined; + var deferredGlobalIteratorType: GenericType | undefined; + var deferredGlobalIterableIteratorType: GenericType | undefined; + var deferredGlobalGeneratorType: GenericType | undefined; + var deferredGlobalIteratorYieldResultType: GenericType | undefined; + var deferredGlobalIteratorReturnResultType: GenericType | undefined; + var deferredGlobalAsyncIterableType: GenericType | undefined; + var deferredGlobalAsyncIteratorType: GenericType | undefined; + var deferredGlobalAsyncIterableIteratorType: GenericType | undefined; + var deferredGlobalAsyncGeneratorType: GenericType | undefined; + var deferredGlobalTemplateStringsArrayType: ObjectType | undefined; + var deferredGlobalImportMetaType: ObjectType; + var deferredGlobalImportMetaExpressionType: ObjectType; + var deferredGlobalImportCallOptionsType: ObjectType | undefined; + var deferredGlobalExtractSymbol: Symbol | undefined; + var deferredGlobalOmitSymbol: Symbol | undefined; + var deferredGlobalAwaitedSymbol: Symbol | undefined; + var deferredGlobalBigIntType: ObjectType | undefined; + var deferredGlobalNaNSymbol: Symbol | undefined; + var deferredGlobalRecordSymbol: Symbol | undefined; + var deferredGlobalClassDecoratorContextType: GenericType | undefined; + var deferredGlobalClassMethodDecoratorContextType: GenericType | undefined; + var deferredGlobalClassGetterDecoratorContextType: GenericType | undefined; + var deferredGlobalClassSetterDecoratorContextType: GenericType | undefined; + var deferredGlobalClassAccessorDecoratorContextType: GenericType | undefined; + var deferredGlobalClassAccessorDecoratorTargetType: GenericType | undefined; + var deferredGlobalClassAccessorDecoratorResultType: GenericType | undefined; + var deferredGlobalClassFieldDecoratorContextType: GenericType | undefined; + + var allPotentiallyUnusedIdentifiers = new Map(); // key is file name + + var flowLoopStart = 0; + var flowLoopCount = 0; + var sharedFlowCount = 0; + var flowAnalysisDisabled = false; + var flowInvocationCount = 0; + var lastFlowNode: FlowNode | undefined; + var lastFlowNodeReachable: boolean; + var flowTypeCache: Type[] | undefined; + + var contextualTypeNodes: Node[] = []; + var contextualTypes: (Type | undefined)[] = []; + var contextualIsCache: boolean[] = []; + var contextualTypeCount = 0; + + var inferenceContextNodes: Node[] = []; + var inferenceContexts: (InferenceContext | undefined)[] = []; + var inferenceContextCount = 0; + + var emptyStringType = getStringLiteralType(""); + var zeroType = getNumberLiteralType(0); + var zeroBigIntType = getBigIntLiteralType({ negative: false, base10Value: "0" }); + + var resolutionTargets: TypeSystemEntity[] = []; + var resolutionResults: boolean[] = []; + var resolutionPropertyNames: TypeSystemPropertyName[] = []; + var resolutionStart = 0; + var inVarianceComputation = false; + + var suggestionCount = 0; + var maximumSuggestionCount = 10; + var mergedSymbols: Symbol[] = []; + var symbolLinks: SymbolLinks[] = []; + var nodeLinks: NodeLinks[] = []; + var flowLoopCaches: Map[] = []; + var flowLoopNodes: FlowNode[] = []; + var flowLoopKeys: string[] = []; + var flowLoopTypes: Type[][] = []; + var sharedFlowNodes: FlowNode[] = []; + var sharedFlowTypes: FlowType[] = []; + var flowNodeReachable: (boolean | undefined)[] = []; + var flowNodePostSuper: (boolean | undefined)[] = []; + var potentialThisCollisions: Node[] = []; + var potentialNewTargetCollisions: Node[] = []; + var potentialWeakMapSetCollisions: Node[] = []; + var potentialReflectCollisions: Node[] = []; + var potentialUnusedRenamedBindingElementsInTypes: BindingElement[] = []; + var awaitedTypeStack: number[] = []; + + var diagnostics = createDiagnosticCollection(); + var suggestionDiagnostics = createDiagnosticCollection(); + + var typeofType = createTypeofType(); + + var _jsxNamespace: __String; + var _jsxFactoryEntity: EntityName | undefined; + + var subtypeRelation = new Map(); + var strictSubtypeRelation = new Map(); + var assignableRelation = new Map(); + var comparableRelation = new Map(); + var identityRelation = new Map(); + var enumRelation = new Map(); + + var builtinGlobals = createSymbolTable(); + builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol); + + // Extensions suggested for path imports when module resolution is node16 or higher. + // The first element of each tuple is the extension a file has. + // The second element of each tuple is the extension that should be used in a path import. + // e.g. if we want to import file `foo.mts`, we should write `import {} from "./foo.mjs". + var suggestedExtensions: [string, string][] = [ + [".mts", ".mjs"], + [".ts", ".js"], + [".cts", ".cjs"], + [".mjs", ".mjs"], + [".js", ".js"], + [".cjs", ".cjs"], + [".tsx", compilerOptions.jsx === JsxEmit.Preserve ? ".jsx" : ".js"], + [".jsx", ".jsx"], + [".json", ".json"], + ]; + /* eslint-enable no-var */ + + initializeTypeChecker(); + + return checker; + + function getCachedType(key: string | undefined) { + return key ? cachedTypes.get(key) : undefined; + } + + function setCachedType(key: string | undefined, type: Type) { + if (key) cachedTypes.set(key, type); + return type; + } + + function getJsxNamespace(location: Node | undefined): __String { + if (location) { + const file = getSourceFileOfNode(location); + if (file) { + if (isJsxOpeningFragment(location)) { + if (file.localJsxFragmentNamespace) { + return file.localJsxFragmentNamespace; } - }) + const jsxFragmentPragma = file.pragmas.get("jsxfrag"); + if (jsxFragmentPragma) { + const chosenPragma = isArray(jsxFragmentPragma) ? jsxFragmentPragma[0] : jsxFragmentPragma; + file.localJsxFragmentFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); + visitNode(file.localJsxFragmentFactory, markAsSynthetic, isEntityName); + if (file.localJsxFragmentFactory) { + return file.localJsxFragmentNamespace = getFirstIdentifier(file.localJsxFragmentFactory).escapedText; + } + } + const entity = getJsxFragmentFactoryEntity(location); + if (entity) { + file.localJsxFragmentFactory = entity; + return file.localJsxFragmentNamespace = getFirstIdentifier(entity).escapedText; + } + } + else { + const localJsxNamespace = getLocalJsxNamespace(file); + if (localJsxNamespace) { + return file.localJsxNamespace = localJsxNamespace; + } + } } } - - function shouldInherit(symbol: Symbol) { - const tags = typeSymbolCache.get(symbol); - if (tags) { - for (const tag of tags) { - if (noInherit.has(tag)) return false; + if (!_jsxNamespace) { + _jsxNamespace = "React" as __String; + if (compilerOptions.jsxFactory) { + _jsxFactoryEntity = parseIsolatedEntityName(compilerOptions.jsxFactory, languageVersion); + visitNode(_jsxFactoryEntity, markAsSynthetic); + if (_jsxFactoryEntity) { + _jsxNamespace = getFirstIdentifier(_jsxFactoryEntity).escapedText; } } - return true; + else if (compilerOptions.reactNamespace) { + _jsxNamespace = escapeLeadingUnderscores(compilerOptions.reactNamespace); + } + } + if (!_jsxFactoryEntity) { + _jsxFactoryEntity = factory.createQualifiedName(factory.createIdentifier(unescapeLeadingUnderscores(_jsxNamespace)), "createElement"); } + return _jsxNamespace; } - function collectRelevantSymbols(target: Type) { - const symbols = new Set(); - for (const symbol of collectRelevantSymbolsWorker(getBaseConstraintOrType(target))) { - symbols.add(symbol) + + function getLocalJsxNamespace(file: SourceFile): __String | undefined { + if (file.localJsxNamespace) { + return file.localJsxNamespace; } - if (symbols.size === 0) { - for (const symbol of collectRelevantSymbolsWorker(target)) { - symbols.add(symbol) + const jsxPragma = file.pragmas.get("jsx"); + if (jsxPragma) { + const chosenPragma = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma; + file.localJsxFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); + visitNode(file.localJsxFactory, markAsSynthetic, isEntityName); + if (file.localJsxFactory) { + return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText; } } - return arrayFrom(symbols.values()); } - /** - * Recursively collects the symbols associated with the given type. Index 0 is the given type's symbol, - * followed by subtypes, subtypes of subtypes, etc. - */ - function collectRelevantSymbolsWorker(target: Type) { - let returnArray = arrayFrom(collectRelevantSymbolsLoop(target, new Set()).values()) - // collect primitive symbols last, in case they have overridden extensions - if (target.flags & TypeFlags.StringLike) { - returnArray.push(tsplusStringPrimitiveSymbol); - } - if (target.flags & TypeFlags.NumberLike) { - returnArray.push(tsplusNumberPrimitiveSymbol); + function markAsSynthetic(node: T): 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, ...args: DiagnosticArguments): Diagnostic { + const diagnostic = location + ? createDiagnosticForNode(location, message, ...args) + : createCompilerDiagnostic(message, ...args); + const existing = diagnostics.lookup(diagnostic); + if (existing) { + return existing; } - if (target.flags & TypeFlags.BooleanLike) { - returnArray.push(tsplusBooleanPrimitiveSymbol); + else { + diagnostics.add(diagnostic); + return diagnostic; } - if (target.flags & TypeFlags.BigIntLike) { - returnArray.push(tsplusBigIntPrimitiveSymbol); + } + + function errorSkippedOn(key: keyof CompilerOptions, location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { + const diagnostic = error(location, message, ...args); + diagnostic.skippedOn = key; + return diagnostic; + } + + function createError(location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { + return location + ? createDiagnosticForNode(location, message, ...args) + : createCompilerDiagnostic(message, ...args); + } + + function error(location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { + const diagnostic = createError(location, message, ...args); + diagnostics.add(diagnostic); + return diagnostic; + } + + function addErrorOrSuggestion(isError: boolean, diagnostic: Diagnostic) { + if (isError) { + diagnostics.add(diagnostic); } - if (isFunctionType(target)) { - returnArray.push(tsplusFunctionPrimitiveSymbol); + else { + suggestionDiagnostics.add({ ...diagnostic, category: DiagnosticCategory.Suggestion }); } - if (isTupleType(target)) { - if (target.target.readonly) { - returnArray.push(tsplusReadonlyArraySymbol); - } - else { - returnArray.push(tsplusArraySymbol); + } + function errorOrSuggestion(isError: boolean, location: Node, message: DiagnosticMessage | DiagnosticMessageChain, ...args: DiagnosticArguments): 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, ...args) : createDiagnosticForFileFromMessageChain(file, message)); // eslint-disable-line local/no-in-operator + return; } - if (target.flags & TypeFlags.Object) { - returnArray.push(tsplusObjectPrimitiveSymbol); - } - return returnArray + addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, ...args) : createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(location), location, message)); // eslint-disable-line local/no-in-operator } - function getAllTypeTags(targetType: Type): string[] { - return targetType.symbol?.declarations?.flatMap(collectTsPlusTypeTags) ?? [] + + function errorAndMaybeSuggestAwait( + location: Node, + maybeMissingAwait: boolean, + message: DiagnosticMessage, + ...args: DiagnosticArguments): Diagnostic { + const diagnostic = error(location, message, ...args); + if (maybeMissingAwait) { + const related = createDiagnosticForNode(location, Diagnostics.Did_you_forget_to_use_await); + addRelatedInfo(diagnostic, related); + } + return diagnostic; } - function isInstanceType(type: Type): boolean { - if (!type.symbol) { - return true; + + function addDeprecatedSuggestionWorker(declarations: Node | Node[], diagnostic: DiagnosticWithLocation) { + const deprecatedTag = Array.isArray(declarations) ? forEach(declarations, getJSDocDeprecatedTag) : getJSDocDeprecatedTag(declarations); + if (deprecatedTag) { + addRelatedInfo( + diagnostic, + createDiagnosticForNode(deprecatedTag, Diagnostics.The_declaration_was_marked_as_deprecated_here) + ); } - if (!(type.symbol.flags & SymbolFlags.Class)) { - return true; + // We call `addRelatedInfo()` before adding the diagnostic to prevent duplicates. + suggestionDiagnostics.add(diagnostic); + return diagnostic; + } + + function isDeprecatedSymbol(symbol: Symbol) { + if (length(symbol.declarations) > 1) { + const parentSymbol = getParentOfSymbol(symbol); + if (parentSymbol && parentSymbol.flags & SymbolFlags.Interface) { + return some(symbol.declarations, d => !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated)); + } } - const declaredType = getDeclaredTypeOfClassOrInterface(type.symbol); - if (!declaredType.symbol) { - return true; + return !!(getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Deprecated); + } + + function addDeprecatedSuggestion(location: Node, declarations: Node[], deprecatedEntity: string) { + const diagnostic = createDiagnosticForNode(location, Diagnostics._0_is_deprecated, deprecatedEntity); + return addDeprecatedSuggestionWorker(declarations, diagnostic); + } + + function addDeprecatedSuggestionWithSignature(location: Node, declaration: Node, deprecatedEntity: string | undefined, signatureString: string) { + const diagnostic = deprecatedEntity + ? createDiagnosticForNode(location, Diagnostics.The_signature_0_of_1_is_deprecated, signatureString, deprecatedEntity) + : createDiagnosticForNode(location, Diagnostics._0_is_deprecated, signatureString); + return addDeprecatedSuggestionWorker(declaration, diagnostic); + } + + function createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) { + symbolCount++; + const symbol = new Symbol(flags | SymbolFlags.Transient, name) as TransientSymbol; + symbol.links = new SymbolLinks() as TransientSymbolLinks; + symbol.links.checkFlags = checkFlags || CheckFlags.None; + return symbol; + } + + function createParameter(name: __String, type: Type) { + const symbol = createSymbol(SymbolFlags.FunctionScopedVariable, name); + symbol.links.type = type; + return symbol; + } + + function createProperty(name: __String, type: Type) { + const symbol = createSymbol(SymbolFlags.Property, name); + symbol.links.type = type; + return symbol; + } + + function getExcludedSymbolFlags(flags: SymbolFlags): SymbolFlags { + let result: SymbolFlags = 0; + if (flags & SymbolFlags.BlockScopedVariable) result |= SymbolFlags.BlockScopedVariableExcludes; + if (flags & SymbolFlags.FunctionScopedVariable) result |= SymbolFlags.FunctionScopedVariableExcludes; + if (flags & SymbolFlags.Property) result |= SymbolFlags.PropertyExcludes; + if (flags & SymbolFlags.EnumMember) result |= SymbolFlags.EnumMemberExcludes; + if (flags & SymbolFlags.Function) result |= SymbolFlags.FunctionExcludes; + if (flags & SymbolFlags.Class) result |= SymbolFlags.ClassExcludes; + if (flags & SymbolFlags.Interface) result |= SymbolFlags.InterfaceExcludes; + if (flags & SymbolFlags.RegularEnum) result |= SymbolFlags.RegularEnumExcludes; + if (flags & SymbolFlags.ConstEnum) result |= SymbolFlags.ConstEnumExcludes; + if (flags & SymbolFlags.ValueModule) result |= SymbolFlags.ValueModuleExcludes; + if (flags & SymbolFlags.Method) result |= SymbolFlags.MethodExcludes; + if (flags & SymbolFlags.GetAccessor) result |= SymbolFlags.GetAccessorExcludes; + if (flags & SymbolFlags.SetAccessor) result |= SymbolFlags.SetAccessorExcludes; + if (flags & SymbolFlags.TypeParameter) result |= SymbolFlags.TypeParameterExcludes; + if (flags & SymbolFlags.TypeAlias) result |= SymbolFlags.TypeAliasExcludes; + if (flags & SymbolFlags.Alias) result |= SymbolFlags.AliasExcludes; + return result; + } + + function recordMergedSymbol(target: Symbol, source: Symbol) { + if (!source.mergeId) { + source.mergeId = nextMergeId; + nextMergeId++; } - return getTypeOfSymbol(declaredType.symbol) !== type; + mergedSymbols[source.mergeId] = target; } - function getExtensions(selfNode: Expression) { - const targetType: Type = getTypeOfNode(selfNode); - const isInstance = isInstanceType(targetType); - const symbols = collectRelevantSymbols(targetType); - const copy: Map = new Map(); - const copyFluent: Map> = new Map(); - const typeTags = getAllTypeTags(targetType) - symbols.forEach((target) => { - if (typeSymbolCache.has(target)) { - typeSymbolCache.get(target)!.forEach((typeSymbol) => { - if (!isInstance && typeTags.includes(typeSymbol)) { - return - } - const _static = staticCache.get(typeSymbol); - if (_static) { - _static.forEach((v, k) => { - if (copy.has(k)) { - return; - } - const ext = v(); - if (ext) { - copy.set(k, ext.patched) - } - }); - } - const _fluent = fluentCache.get(typeSymbol); - if (_fluent) { - _fluent.forEach((v, k) => { - const extension = v() - if (extension) { - if (isExtensionValidForTarget(getTypeOfSymbol(extension.patched), targetType)) { - if (!copyFluent.has(k)) { - copyFluent.set(k, new Set()); - } - copyFluent.get(k)!.add(extension); - } - } - }); - } - const _getter = getterCache.get(typeSymbol); - if (_getter) { - _getter.forEach((v, k) => { - if (copy.has(k)) { - return; - } - const symbol = v.patched(selfNode); - if (symbol) { - copy.set(k, symbol); - } - }); - } - }); - } - if (companionSymbolCache.has(target) && isCompanionReference(selfNode)) { - companionSymbolCache.get(target)!.forEach((typeSymbol) => { - const _static = staticCache.get(typeSymbol); - if (_static) { - _static.forEach((v, k) => { - if (copy.has(k)) { - return; - } - const ext = v() - if (ext) { - copy.set(k, ext.patched) - } - }); - } - }) + + function cloneSymbol(symbol: Symbol): TransientSymbol { + const result = createSymbol(symbol.flags, symbol.escapedName); + result.declarations = symbol.declarations ? symbol.declarations.slice() : []; + result.parent = symbol.parent; + if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; + if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; + if (symbol.members) result.members = new Map(symbol.members); + if (symbol.exports) result.exports = new Map(symbol.exports); + recordMergedSymbol(result, symbol); + return result; + } + + /** + * Note: if target is transient, then it is mutable, and mergeSymbol with both mutate and return it. + * If target is not transient, mergeSymbol will produce a transient clone, mutate that and return it. + */ + function mergeSymbol(target: Symbol, source: Symbol, unidirectional = false): Symbol { + if (!(target.flags & getExcludedSymbolFlags(source.flags)) || + (source.flags | target.flags) & SymbolFlags.Assignment) { + if (source === target) { + // This can happen when an export assigned namespace exports something also erroneously exported at the top level + // See `declarationFileNoCrashOnExtraExportModifier` for an example + return target; } - }) - fluentCache.get("global")?.forEach((v, k) => { - const extension = v() - if (extension) { - if (isExtensionValidForTarget(getTypeOfSymbol(extension.patched), targetType)) { - if (!copyFluent.has(k)) { - copyFluent.set(k, new Set()); - } - copyFluent.get(k)!.add(extension); + if (!(target.flags & SymbolFlags.Transient)) { + const resolvedTarget = resolveSymbol(target); + if (resolvedTarget === unknownSymbol) { + return source; } + target = cloneSymbol(resolvedTarget); } - }); - getterCache.get("global")?.forEach((v, k) => { - if (copy.has(k)) { - return; + // 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; } - const symbol = v.patched(selfNode); - if (symbol) { - copy.set(k, symbol); + target.flags |= source.flags; + if (source.valueDeclaration) { + setValueDeclaration(target, source.valueDeclaration); } - }); - copyFluent.forEach((extensions, k) => { - copy.set( - k, - createTsPlusFluentSymbolWithType(k, arrayFrom(flatMapIterator(extensions.values(), (e) => getSignaturesOfType(getTypeOfSymbol(e.patched), SignatureKind.Call))) as TsPlusSignature[]) - ); - }); - copy.delete("__call"); - return copy; - } - function isExtensionValidForTarget(extension: Type, targetType: Type): boolean { - return getSignaturesOfType(extension, SignatureKind.Call).find((candidate) => { - if (candidate.thisParameter) { - const paramType = unionIfLazy(getTypeOfSymbol(candidate.thisParameter)); - if (!candidate.typeParameters) { - return isTypeAssignableTo(targetType, paramType); - } else { - const inferenceContext = createInferenceContext( - candidate.typeParameters, - candidate, - InferenceFlags.None - ); - inferTypes(inferenceContext.inferences, targetType, paramType); - const instantiatedThisType = instantiateType(paramType, inferenceContext.mapper); - return isTypeAssignableTo(targetType, instantiatedThisType); - } + addRange(target.declarations, source.declarations); + if (source.members) { + if (!target.members) target.members = createSymbolTable(); + mergeSymbolTable(target.members, source.members, unidirectional); } - return false; - }) !== undefined; - } - function unionIfLazy(_paramType: Type) { - const isLazy = isLazyParameterByType(_paramType); - const paramType = isLazy ? getUnionType([_paramType, (_paramType as TypeReference).resolvedTypeArguments![0]], UnionReduction.None) : _paramType; - return paramType - } - function getFluentExtension(targetType: Type, name: string): Type | undefined { - const isInstance = isInstanceType(targetType); - const typeTags = getAllTypeTags(targetType) - const symbols = collectRelevantSymbols(targetType); - const candidates: Set = new Set(); - for (const target of symbols) { - if (typeSymbolCache.has(target)) { - const x = typeSymbolCache.get(target)!.flatMap( - (tag) => { - if (!isInstance && typeTags.includes(tag)) { - return [] - } - if (fluentCache.has(tag)) { - const cache = fluentCache.get(tag) - if (cache?.has(name)) { - return [cache.get(name)!] - } - } - return [] - } - ) - if (x.length === 0) { - continue; - } - else { - x.forEach((getExt) => { - const ext = getExt(); - if (ext) { - for (const signature of ext.signatures) { - candidates.add(signature); - } - } - }); - } + if (source.exports) { + if (!target.exports) target.exports = createSymbolTable(); + mergeSymbolTable(target.exports, source.exports, unidirectional); + } + if (!unidirectional) { + recordMergedSymbol(target, source); } } - const globalExtension = fluentCache.get("global")?.get(name)?.(); - if (globalExtension) { - for (const signature of globalExtension.signatures) { - candidates.add(signature) + else if (target.flags & SymbolFlags.NamespaceModule) { + // Do not report an error when merging `var globalThis` with the built-in `globalThis`, + // as we will already report a "Declaration name conflicts..." error, and this error + // won't make much sense. + if (target !== globalThisSymbol) { + error( + source.declarations && getNameOfDeclaration(source.declarations[0]), + Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, + symbolToString(target)); } } - if (candidates.size > 0) { - return getTypeOfSymbol(createTsPlusFluentSymbolWithType(name, arrayFrom(candidates.values()))); + 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 isSourcePlainJs = isPlainJsFile(sourceSymbolFile, compilerOptions.checkJs); + const isTargetPlainJs = isPlainJsFile(targetSymbolFile, compilerOptions.checkJs); + const symbolName = symbolToString(source); + + // Collect top-level duplicate identifier errors into one mapping, so we can then merge their diagnostics if there are a bunch + if (sourceSymbolFile && targetSymbolFile && amalgamatedDuplicates && !isEitherEnum && sourceSymbolFile !== targetSymbolFile) { + const firstFile = comparePaths(sourceSymbolFile.path, targetSymbolFile.path) === Comparison.LessThan ? sourceSymbolFile : targetSymbolFile; + const secondFile = firstFile === sourceSymbolFile ? targetSymbolFile : sourceSymbolFile; + const filesDuplicates = getOrUpdate(amalgamatedDuplicates, `${firstFile.path}|${secondFile.path}`, (): DuplicateInfoForFiles => + ({ firstFile, secondFile, conflictingSymbols: new Map() })); + const conflictingSymbolInfo = getOrUpdate(filesDuplicates.conflictingSymbols, symbolName, (): DuplicateInfoForSymbol => + ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] })); + if (!isSourcePlainJs) addDuplicateLocations(conflictingSymbolInfo.firstFileLocations, source); + if (!isTargetPlainJs) addDuplicateLocations(conflictingSymbolInfo.secondFileLocations, target); + } + else { + if (!isSourcePlainJs) addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target); + if (!isTargetPlainJs) addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source); + } } - } - function getGetterExtension(targetType: Type, name: string) { - const symbols = collectRelevantSymbols(targetType) - const isInstance = isInstanceType(targetType); - const typeTags = getAllTypeTags(targetType) - for (const target of symbols) { - if (typeSymbolCache.has(target)) { - const x = typeSymbolCache.get(target)!.flatMap( - (tag) => { - if (!isInstance && typeTags.includes(tag)) { - return [] - } - if (getterCache.has(tag)) { - const cache = getterCache.get(tag) - if (cache?.has(name)) { - return [cache.get(name)!] - } - } - return [] - } - ) - if (x.length === 0) { - continue; - } - else { - return x[x.length - 1]; + return target; + + function addDuplicateLocations(locs: Declaration[], symbol: Symbol): void { + if (symbol.declarations) { + for (const decl of symbol.declarations) { + pushIfUnique(locs, decl); } } } - return getterCache.get("global")?.get(name); } - function getGetterCompanionExtension(targetType: Type, name: string) { - const symbols = collectRelevantSymbols(targetType) - for (const target of symbols) { - if (companionSymbolCache.has(target)) { - const x = companionSymbolCache.get(target)!.flatMap( - (tag) => { - if (getterCache.has(tag)) { - const cache = getterCache.get(tag) - if (cache?.has(name)) { - return [cache.get(name)!] - } - } - return [] - } - ) - if (x.length === 0) { - continue; - } - else { - return x[x.length - 1]; - } - } + + 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 getStaticExtension(targetType: Type, name: string) { - const symbols = collectRelevantSymbols(targetType) - for (const target of symbols) { - if (typeSymbolCache.has(target)) { - const x = typeSymbolCache.get(target)!.flatMap( - (tag) => { - if (staticCache.has(tag)) { - const cache = staticCache.get(tag) - if (cache?.has(name)) { - return [cache.get(name)!] - } - } - return [] + + 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) : getMergedSymbol(sourceSymbol)); + }); + } + + function mergeModuleAugmentation(moduleName: StringLiteral | Identifier): void { + const moduleAugmentation = moduleName.parent as ModuleDeclaration; + if (moduleAugmentation.symbol.declarations?.[0] !== moduleAugmentation) { + // this is a combined symbol for multiple augmentations within the same file. + // its symbol already has accumulated information for all declarations + // so we need to add it just once - do the work only for first declaration + Debug.assert(moduleAugmentation.symbol.declarations!.length > 1); + return; + } + + if (isGlobalScopeAugmentation(moduleAugmentation)) { + mergeSymbolTable(globals, moduleAugmentation.symbol.exports!); + } + else { + // find a module that about to be augmented + // do not validate names of augmentations that are defined in ambient context + const moduleNotFoundError = !(moduleName.parent.parent.flags & NodeFlags.Ambient) + ? Diagnostics.Invalid_module_name_in_augmentation_module_0_cannot_be_found + : undefined; + let mainModule = resolveExternalModuleNameWorker(moduleName, moduleName, moduleNotFoundError, /*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(); } - ) - if (x.length === 0) { - continue; + // moduleName will be a StringLiteral since this is not `declare global`. + patternAmbientModuleAugmentations.set((moduleName as StringLiteral).text, merged); } else { - return x[x.length - 1](); - } - } - } - } - function getStaticCompanionExtension(targetType: Type, name: string) { - const symbols = collectRelevantSymbols(targetType) - for (const target of symbols) { - if (companionSymbolCache.has(target)) { - const x = companionSymbolCache.get(target)!.flatMap( - (tag) => { - if (staticCache.has(tag)) { - const cache = staticCache.get(tag) - if (cache?.has(name)) { - return [cache.get(name)!] + 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); } } - return [] } - ) - if (x.length === 0) { - continue; - } - else { - return x[x.length - 1](); + mergeSymbol(mainModule, moduleAugmentation.symbol); } } - } - } - function isTransformablePipeableExtension(type: Type): boolean { - if (type.symbol) { - if (isTsPlusSymbol(type.symbol)) { - if (type.symbol.tsPlusTag === TsPlusSymbolTag.PipeableIdentifier) { - const fluent = getFluentExtensionForPipeableSymbol(type.symbol); - if (fluent) { - return true; - } - } - if (type.symbol.tsPlusTag === TsPlusSymbolTag.PipeableMacro) { - return true; - } + 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); } } - return false; } - function markUsedParams(params: readonly TypeParameterDeclaration[], types: readonly Type[], cache: Set, node: Node) { - function visitor(node: Node): Node { - const t = getTypeOfNode(node); - for (let i = 0; i < params.length; i++) { - const type = types[i]; - if (isTypeIdenticalTo(type, t)) { - cache.add(params[i]); - } + + 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)); } - return visitEachChild(node, visitor, nullTransformationContext); + else { + target.set(id, sourceSymbol); + } + }); + + function addDeclarationDiagnostic(id: string, message: DiagnosticMessage) { + return (declaration: Declaration) => diagnostics.add(createDiagnosticForNode(declaration, message, id)); } - visitNode(node, visitor); } - function partitionTypeParametersForPipeable(dataFirst: FunctionDeclaration | ArrowFunction | FunctionExpression): [TypeParameterDeclaration[] | undefined, TypeParameterDeclaration[] | undefined] { - if (!dataFirst.typeParameters) { - return [undefined, undefined]; - } - const typeParams = dataFirst.typeParameters; - const types = map(typeParams, getTypeOfNode); - const cache = new Set() - for (let i = 1; i < dataFirst.parameters.length; i++) { - const param = dataFirst.parameters[i]; - markUsedParams(typeParams, types, cache, param); - } - let loop = true; - const processed = new Set(); - while (loop) { - const pre = cache.size; - cache.forEach((typeParam) => { - if (!processed.has(typeParam)) { - processed.add(typeParam); - if (typeParam.constraint) { - markUsedParams(typeParams, types, cache, typeParam.constraint); - } - if (typeParam.default) { - markUsedParams(typeParams, types, cache, typeParam.default); + + function getSymbolLinks(symbol: Symbol): SymbolLinks { + if (symbol.flags & SymbolFlags.Transient) return (symbol as TransientSymbol).links; + const id = getSymbolId(symbol); + return symbolLinks[id] ??= new SymbolLinks(); + } + + function getNodeLinks(node: Node): NodeLinks { + const nodeId = getNodeId(node); + return nodeLinks[nodeId] || (nodeLinks[nodeId] = new (NodeLinks as any)()); + } + + function isGlobalSourceFile(node: Node) { + return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(node as SourceFile); + } + + 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 targetFlags = getAllSymbolFlags(symbol); + // `targetFlags` will be `SymbolFlags.All` if an error occurred in alias resolution; this avoids cascading errors + if (targetFlags & meaning) { + return symbol; } } - }) - if (cache.size === pre) { - loop = false; } } - const left: TypeParameterDeclaration[] = []; - const right: TypeParameterDeclaration[] = []; - forEach(dataFirst.typeParameters, (param) => { - if (cache.has(param)) { - left.push(param); - } - else { - right.push(param); - } - }); - return [ - left.length > 0 ? left : undefined, - right.length > 0 ? right : undefined - ]; - } - function generatePipeable(declarationNode: VariableDeclaration, dataFirst: FunctionDeclaration | ArrowFunction | FunctionExpression): Type | undefined { - const signatures = getSignaturesOfType(getTypeOfNode(dataFirst), SignatureKind.Call) - if (signatures.length > 0) { - const returnExpression = createSyntheticExpression(dataFirst, getReturnTypeOfSignature(signatures[0])) - const [paramsFirst, paramsSecond] = partitionTypeParametersForPipeable(dataFirst); - const returnFunction = factory.createFunctionExpression( - undefined, - undefined, - undefined, - paramsSecond, - [dataFirst.parameters[0]], - undefined, - factory.createBlock( - [factory.createReturnStatement(returnExpression)], - true - ) - ); - setParent(returnFunction.body, returnFunction); - setParent((returnFunction.body).statements[0], returnFunction.body); - returnFunction.locals = createSymbolTable(); - const returnFunctionSymbol = createSymbol( - SymbolFlags.Function, - InternalSymbolName.Function - ); - returnFunction.symbol = returnFunctionSymbol; - returnFunctionSymbol.declarations = [returnFunction]; - returnFunctionSymbol.valueDeclaration = returnFunction; - const pipeable = factory.createFunctionDeclaration( - [factory.createModifier(SyntaxKind.DeclareKeyword)], - undefined, - dataFirst.name, - paramsFirst, - dataFirst.parameters.slice(1, dataFirst.parameters.length), - undefined, - factory.createBlock( - [factory.createReturnStatement(returnFunction)], - true - ) - ); - setParent(returnFunction, pipeable); - setParent(pipeable.body, pipeable); - setParent((pipeable.body as Block).statements[0], pipeable.body); - pipeable.locals = createSymbolTable(); - const pipeableSymbol = createSymbol( - SymbolFlags.Function, - InternalSymbolName.Function - ) as TsPlusPipeableMacroSymbol; - pipeableSymbol.tsPlusTag = TsPlusSymbolTag.PipeableMacro; - pipeableSymbol.tsPlusDeclaration = declarationNode; - pipeableSymbol.tsPlusDataFirst = dataFirst; - pipeableSymbol.tsPlusSourceFile = getSourceFileOfNode(dataFirst); - pipeableSymbol.tsPlusExportName = dataFirst.name!.escapedText.toString(); - pipeable.symbol = pipeableSymbol; - pipeableSymbol.declarations = [pipeable]; - setParent(pipeable, declarationNode); - const declarationSymbol = getSymbolAtLocation(declarationNode) - if (declarationSymbol) { - declarationSymbol.declarations = [pipeable] - declarationSymbol.valueDeclaration = pipeable - } - return getTypeOfNode(pipeable); - } - } - function isTsPlusMacroGetter(node: Node, macro: string): boolean { - const links = getNodeLinks(node) - return !!links.tsPlusGetterExtension && - !!links.tsPlusGetterExtension.declaration.tsPlusMacroTags && - links.tsPlusGetterExtension.declaration.tsPlusMacroTags.includes(macro); + // return undefined if we can't find a symbol. } - function isTsPlusMacroCall(node: Node, macro: K): node is TsPlusMacroCallExpression { - if (!isCallExpression(node) && !isBinaryExpression(node)) { - return false; - } - const links = getNodeLinks(isCallExpression(node) ? node : node.operatorToken); - if ( - links.resolvedSignature && - links.resolvedSignature.declaration && - collectTsPlusMacroTags(links.resolvedSignature.declaration).findIndex((tag) => tag === macro) !== -1 - ) { - return true; + + /** + * Get symbols that represent parameter-property-declaration as parameter and as property declaration + * @param parameter a parameterDeclaration node + * @param parameterName a name of the parameter to get the symbols for. + * @return a tuple of two symbols + */ + function getSymbolsOfParameterPropertyDeclaration(parameter: ParameterPropertyDeclaration, parameterName: __String): [Symbol, Symbol] { + const constructorDeclaration = parameter.parent; + const classDeclaration = parameter.parent.parent; + + const parameterSymbol = getSymbol(constructorDeclaration.locals!, parameterName, SymbolFlags.Value); + const propertySymbol = getSymbol(getMembersOfSymbol(classDeclaration.symbol), parameterName, SymbolFlags.Value); + + if (parameterSymbol && propertySymbol) { + return [parameterSymbol, propertySymbol]; } - return false; - } - // TSPLUS EXTENSION END - function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined { - const node = getParseTreeNode(nodeIn, isCallLikeExpression); - apparentArgumentCount = argumentCount; - const res = !node ? undefined : getResolvedSignature(node, candidatesOutArray, checkMode); - apparentArgumentCount = undefined; - return res; + return Debug.fail("There should exist two symbols, one as property declaration and one as parameter declaration"); } - var tupleTypes = new Map(); - var unionTypes = new Map(); - var unionOfUnionTypes = new Map(); - var intersectionTypes = new Map(); - var stringLiteralTypes = new Map(); - var numberLiteralTypes = new Map(); - var bigIntLiteralTypes = new Map(); - var enumLiteralTypes = new Map(); - var indexedAccessTypes = new Map(); - var templateLiteralTypes = new Map(); - var stringMappingTypes = new Map(); - var substitutionTypes = new Map(); - var subtypeReductionCache = new Map(); - var decoratorContextOverrideTypeCache = new Map(); - var cachedTypes = new Map(); - var evolvingArrayTypes: EvolvingArrayType[] = []; - var undefinedProperties: SymbolTable = new Map(); - var markerTypes = new Set(); - - var unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String); - var resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving); - var unresolvedSymbols = new Map(); - var errorTypes = new Map(); + 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); + } - // We specifically create the `undefined` and `null` types before any other types that can occur in - // unions such that they are given low type IDs and occur first in the sorted list of union constituents. - // We can then just examine the first constituent(s) of a union to check for their presence. + 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 !(getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields + && getContainingClass(declaration) === getContainingClass(usage) + && isUsedInFunctionOrInstanceProperty(usage, declaration)); + } + return true; + } - var anyType = createIntrinsicType(TypeFlags.Any, "any"); - var autoType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.NonInferrableType); - var wildcardType = createIntrinsicType(TypeFlags.Any, "any"); - var errorType = createIntrinsicType(TypeFlags.Any, "error"); - var unresolvedType = createIntrinsicType(TypeFlags.Any, "unresolved"); - var nonInferrableAnyType = createIntrinsicType(TypeFlags.Any, "any", ObjectFlags.ContainsWideningType); - var intrinsicMarkerType = createIntrinsicType(TypeFlags.Any, "intrinsic"); - var unknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); - var nonNullUnknownType = createIntrinsicType(TypeFlags.Unknown, "unknown"); - var undefinedType = createIntrinsicType(TypeFlags.Undefined, "undefined"); - var undefinedWideningType = strictNullChecks ? undefinedType : createIntrinsicType(TypeFlags.Undefined, "undefined", ObjectFlags.ContainsWideningType); - var missingType = createIntrinsicType(TypeFlags.Undefined, "undefined"); - var undefinedOrMissingType = exactOptionalPropertyTypes ? missingType : undefinedType; - var optionalType = createIntrinsicType(TypeFlags.Undefined, "undefined"); - var nullType = createIntrinsicType(TypeFlags.Null, "null"); - var nullWideningType = strictNullChecks ? nullType : createIntrinsicType(TypeFlags.Null, "null", ObjectFlags.ContainsWideningType); - var stringType = createIntrinsicType(TypeFlags.String, "string"); - var numberType = createIntrinsicType(TypeFlags.Number, "number"); - var bigintType = createIntrinsicType(TypeFlags.BigInt, "bigint"); - var falseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; - var regularFalseType = createIntrinsicType(TypeFlags.BooleanLiteral, "false") as FreshableIntrinsicType; - var trueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType; - var regularTrueType = createIntrinsicType(TypeFlags.BooleanLiteral, "true") as FreshableIntrinsicType; - trueType.regularType = regularTrueType; - trueType.freshType = trueType; - regularTrueType.regularType = regularTrueType; - regularTrueType.freshType = trueType; - falseType.regularType = regularFalseType; - falseType.freshType = falseType; - regularFalseType.regularType = regularFalseType; - regularFalseType.freshType = falseType; - var booleanType = getUnionType([regularFalseType, regularTrueType]); - var esSymbolType = createIntrinsicType(TypeFlags.ESSymbol, "symbol"); - var voidType = createIntrinsicType(TypeFlags.Void, "void"); - var neverType = createIntrinsicType(TypeFlags.Never, "never"); - var silentNeverType = createIntrinsicType(TypeFlags.Never, "never", ObjectFlags.NonInferrableType); - var implicitNeverType = createIntrinsicType(TypeFlags.Never, "never"); - var unreachableNeverType = createIntrinsicType(TypeFlags.Never, "never"); - var nonPrimitiveType = createIntrinsicType(TypeFlags.NonPrimitive, "object"); - var stringOrNumberType = getUnionType([stringType, numberType]); - var stringNumberSymbolType = getUnionType([stringType, numberType, esSymbolType]); - var keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType; - var numberOrBigIntType = getUnionType([numberType, bigintType]); - var templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType, nullType, undefinedType]) as UnionType; - var numericStringType = getTemplateLiteralType(["", ""], [numberType]); // The `${number}` type - var restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(t as TypeParameter) : t, () => "(restrictive mapper)"); - var permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t, () => "(permissive mapper)"); - var uniqueLiteralType = createIntrinsicType(TypeFlags.Never, "never"); // `uniqueLiteralType` is a special `never` flagged by union reduction to behave as a literal - var uniqueLiteralMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? uniqueLiteralType : t, () => "(unique literal mapper)"); // replace all type parameters with the unique literal type (disregarding constraints) - var outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined; - var reportUnreliableMapper = makeFunctionTypeMapper(t => { - if (outofbandVarianceMarkerHandler && (t === markerSuperType || t === markerSubType || t === markerOtherType)) { - outofbandVarianceMarkerHandler(/*onlyUnreliable*/ true); + // 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; } - return t; - }, () => "(unmeasurable reporter)"); - var reportUnmeasurableMapper = makeFunctionTypeMapper(t => { - if (outofbandVarianceMarkerHandler && (t === markerSuperType || t === markerSubType || t === markerOtherType)) { - outofbandVarianceMarkerHandler(/*onlyUnreliable*/ false); + // 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; } - return t; - }, () => "(unreliable reporter)"); - var emptyObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - var emptyJsxObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - emptyJsxObjectType.objectFlags |= ObjectFlags.JsxAttributes; + if (!!(usage.flags & NodeFlags.JSDoc) || isInTypeQuery(usage) || isInAmbientOrTypeNode(usage)) { + return true; + } + if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { + if (getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields + && getContainingClass(declaration) + && (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent))) { + return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true); + } + else { + return true; + } + } + return false; - var emptyTypeLiteralSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); - emptyTypeLiteralSymbol.members = createSymbolTable(); - var emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, emptyArray); + 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; + } - var unknownEmptyObjectType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - var unknownUnionType = strictNullChecks ? getUnionType([undefinedType, nullType, unknownEmptyObjectType]) : unknownType; + // 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); + } - var emptyGenericType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray) as ObjectType as GenericType; - emptyGenericType.instantiations = new Map(); + function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node): boolean { + return !!findAncestor(usage, current => { + if (current === declContainer) { + return "quit"; + } + if (isFunctionLike(current)) { + return true; + } + if (isClassStaticBlockDeclaration(current)) { + return declaration.pos < usage.pos; + } - var anyFunctionType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated - // in getPropagatingFlagsOfTypes, and it is checked in inferFromTypes. - anyFunctionType.objectFlags |= ObjectFlags.NonInferrableType; + const propertyDeclaration = tryCast(current.parent, isPropertyDeclaration); + if (propertyDeclaration) { + const initializerOfProperty = propertyDeclaration.initializer === current; + if (initializerOfProperty) { + if (isStatic(current.parent)) { + if (declaration.kind === SyntaxKind.MethodDeclaration) { + return true; + } + if (isPropertyDeclaration(declaration) && getContainingClass(usage) === getContainingClass(declaration)) { + const propName = declaration.name; + if (isIdentifier(propName) || isPrivateIdentifier(propName)) { + const type = getTypeOfSymbol(getSymbolOfDeclaration(declaration)); + const staticBlocks = filter(declaration.parent.members, isClassStaticBlockDeclaration); + if (isPropertyInitializedInStaticBlocks(propName, type, staticBlocks, declaration.parent.pos, current.pos)) { + return true; + } + } + } + } + else { + const isDeclarationInstanceProperty = declaration.kind === SyntaxKind.PropertyDeclaration && !isStatic(declaration); + if (!isDeclarationInstanceProperty || getContainingClass(usage) !== getContainingClass(declaration)) { + return true; + } + } + } + } + return false; + }); + } - var noConstraintType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - var circularConstraintType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); - var resolvingDefaultType = createAnonymousType(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + /** 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; + } - var markerSuperType = createTypeParameter(); - var markerSubType = createTypeParameter(); - markerSubType.constraint = markerSuperType; - var markerOtherType = createTypeParameter(); + // 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"; + } - var markerSuperTypeForCheck = createTypeParameter(); - var markerSubTypeForCheck = createTypeParameter(); - markerSubTypeForCheck.constraint = markerSuperTypeForCheck; + 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; + } + }); - var noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<>", 0, anyType); + return ancestorChangingReferenceScope === undefined; + } + } - var anySignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - var unknownSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - var resolvingSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); - var silentNeverSignature = createSignature(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + function useOuterVariableScopeInParameter(result: Symbol, location: Node, lastLocation: Node) { + const target = getEmitScriptTarget(compilerOptions); + const functionLocation = location as FunctionLikeDeclaration; + if (isParameter(lastLocation) + && functionLocation.body + && result.valueDeclaration + && 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; - var enumNumberIndexInfo = createIndexInfo(numberType, stringType, /*isReadonly*/ true); + function requiresScopeChange(node: ParameterDeclaration): boolean { + return requiresScopeChangeWorker(node.name) + || !!node.initializer && requiresScopeChangeWorker(node.initializer); + } - var iterationTypesCache = new Map(); // cache for common IterationTypes instances - var noIterationTypes: IterationTypes = { - get yieldType(): Type { return Debug.fail("Not supported"); }, - get returnType(): Type { return Debug.fail("Not supported"); }, - get nextType(): Type { return Debug.fail("Not supported"); }, - }; + 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 || !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; + } + } + } - var anyIterationTypes = createIterationTypes(anyType, anyType, anyType); - var anyIterationTypesExceptNext = createIterationTypes(anyType, anyType, unknownType); - var defaultIterationTypes = createIterationTypes(neverType, anyType, undefinedType); // default iteration types for `Iterator`. + function isConstAssertion(location: Node) { + return (isAssertionExpression(location) && isConstTypeReference(location.type)) + || (isJSDocTypeTag(location) && isConstTypeReference(location.typeExpression)); + } - var 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, - }; - - var 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: Map; - } - /** Key is "/path/to/a.ts|/path/to/b.ts". */ - var amalgamatedDuplicates: Map | undefined; - var reverseMappedCache = new Map(); - var inInferTypeForHomomorphicMappedType = false; - var ambientModulesCache: Symbol[] | undefined; - /** - * List of every ambient module with a "*" wildcard. - * Unlike other ambient modules, these can't be stored in `globals` because symbol tables only deal with exact matches. - * This is only used if there is no exact match. - */ - var patternAmbientModules: PatternAmbientModule[]; - var patternAmbientModuleAugmentations: Map | undefined; - - var globalObjectType: ObjectType; - var globalFunctionType: ObjectType; - var globalCallableFunctionType: ObjectType; - var globalNewableFunctionType: ObjectType; - var globalArrayType: GenericType; - var globalReadonlyArrayType: GenericType; - var globalStringType: ObjectType; - var globalNumberType: ObjectType; - var globalBooleanType: ObjectType; - var globalRegExpType: ObjectType; - var globalThisType: GenericType; - var anyArrayType: Type; - var autoArrayType: Type; - var anyReadonlyArrayType: Type; - var deferredGlobalNonNullableTypeAlias: Symbol; - - // The library files are only loaded when the feature is used. - // This allows users to just specify library files they want to used through --lib - // and they will not get an error from not having unrelated library files - var deferredGlobalESSymbolConstructorSymbol: Symbol | undefined; - var deferredGlobalESSymbolConstructorTypeSymbol: Symbol | undefined; - var deferredGlobalESSymbolType: ObjectType | undefined; - var deferredGlobalTypedPropertyDescriptorType: GenericType; - var deferredGlobalPromiseType: GenericType | undefined; - var deferredGlobalPromiseLikeType: GenericType | undefined; - var deferredGlobalPromiseConstructorSymbol: Symbol | undefined; - var deferredGlobalPromiseConstructorLikeType: ObjectType | undefined; - var deferredGlobalIterableType: GenericType | undefined; - var deferredGlobalIteratorType: GenericType | undefined; - var deferredGlobalIterableIteratorType: GenericType | undefined; - var deferredGlobalGeneratorType: GenericType | undefined; - var deferredGlobalIteratorYieldResultType: GenericType | undefined; - var deferredGlobalIteratorReturnResultType: GenericType | undefined; - var deferredGlobalAsyncIterableType: GenericType | undefined; - var deferredGlobalAsyncIteratorType: GenericType | undefined; - var deferredGlobalAsyncIterableIteratorType: GenericType | undefined; - var deferredGlobalAsyncGeneratorType: GenericType | undefined; - var deferredGlobalTemplateStringsArrayType: ObjectType | undefined; - var deferredGlobalImportMetaType: ObjectType; - var deferredGlobalImportMetaExpressionType: ObjectType; - var deferredGlobalImportCallOptionsType: ObjectType | undefined; - var deferredGlobalExtractSymbol: Symbol | undefined; - var deferredGlobalOmitSymbol: Symbol | undefined; - var deferredGlobalAwaitedSymbol: Symbol | undefined; - var deferredGlobalBigIntType: ObjectType | undefined; - var deferredGlobalNaNSymbol: Symbol | undefined; - var deferredGlobalRecordSymbol: Symbol | undefined; - var deferredGlobalClassDecoratorContextType: GenericType | undefined; - var deferredGlobalClassMethodDecoratorContextType: GenericType | undefined; - var deferredGlobalClassGetterDecoratorContextType: GenericType | undefined; - var deferredGlobalClassSetterDecoratorContextType: GenericType | undefined; - var deferredGlobalClassAccessorDecoratorContextType: GenericType | undefined; - var deferredGlobalClassAccessorDecoratorTargetType: GenericType | undefined; - var deferredGlobalClassAccessorDecoratorResultType: GenericType | undefined; - var deferredGlobalClassFieldDecoratorContextType: GenericType | undefined; - - var allPotentiallyUnusedIdentifiers = new Map(); // key is file name - - var flowLoopStart = 0; - var flowLoopCount = 0; - var sharedFlowCount = 0; - var flowAnalysisDisabled = false; - var flowInvocationCount = 0; - var lastFlowNode: FlowNode | undefined; - var lastFlowNodeReachable: boolean; - var flowTypeCache: Type[] | undefined; - - var contextualTypeNodes: Node[] = []; - var contextualTypes: (Type | undefined)[] = []; - var contextualIsCache: boolean[] = []; - var contextualTypeCount = 0; - - var inferenceContextNodes: Node[] = []; - var inferenceContexts: (InferenceContext | undefined)[] = []; - var inferenceContextCount = 0; - - var emptyStringType = getStringLiteralType(""); - var zeroType = getNumberLiteralType(0); - var zeroBigIntType = getBigIntLiteralType({ negative: false, base10Value: "0" }); - - var resolutionTargets: TypeSystemEntity[] = []; - var resolutionResults: boolean[] = []; - var resolutionPropertyNames: TypeSystemPropertyName[] = []; - var resolutionStart = 0; - var inVarianceComputation = false; - - var suggestionCount = 0; - var maximumSuggestionCount = 10; - var mergedSymbols: Symbol[] = []; - var symbolLinks: SymbolLinks[] = []; - var nodeLinks: NodeLinks[] = []; - var flowLoopCaches: Map[] = []; - var flowLoopNodes: FlowNode[] = []; - var flowLoopKeys: string[] = []; - var flowLoopTypes: Type[][] = []; - var sharedFlowNodes: FlowNode[] = []; - var sharedFlowTypes: FlowType[] = []; - var flowNodeReachable: (boolean | undefined)[] = []; - var flowNodePostSuper: (boolean | undefined)[] = []; - var potentialThisCollisions: Node[] = []; - var potentialNewTargetCollisions: Node[] = []; - var potentialWeakMapSetCollisions: Node[] = []; - var potentialReflectCollisions: Node[] = []; - var potentialUnusedRenamedBindingElementsInTypes: BindingElement[] = []; - var awaitedTypeStack: number[] = []; - - var diagnostics = createDiagnosticCollection(); - var suggestionDiagnostics = createDiagnosticCollection(); - - var typeofType = createTypeofType(); - - var _jsxNamespace: __String; - var _jsxFactoryEntity: EntityName | undefined; - - var subtypeRelation = new Map(); - var strictSubtypeRelation = new Map(); - var assignableRelation = new Map(); - var comparableRelation = new Map(); - var identityRelation = new Map(); - var enumRelation = new Map(); - - var builtinGlobals = createSymbolTable(); - builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol); - - // Extensions suggested for path imports when module resolution is node16 or higher. - // The first element of each tuple is the extension a file has. - // The second element of each tuple is the extension that should be used in a path import. - // e.g. if we want to import file `foo.mts`, we should write `import {} from "./foo.mjs". - var suggestedExtensions: [string, string][] = [ - [".mts", ".mjs"], - [".ts", ".js"], - [".cts", ".cjs"], - [".mjs", ".mjs"], - [".js", ".js"], - [".cjs", ".cjs"], - [".tsx", compilerOptions.jsx === JsxEmit.Preserve ? ".jsx" : ".js"], - [".jsx", ".jsx"], - [".json", ".json"], - ]; - /* eslint-enable no-var */ - - initializeTypeChecker(); - - return checker; - - function getCachedType(key: string | undefined) { - return key ? cachedTypes.get(key) : undefined; - } - - function setCachedType(key: string | undefined, type: Type) { - if (key) cachedTypes.set(key, type); - return type; - } - - function getJsxNamespace(location: Node | undefined): __String { - if (location) { - const file = getSourceFileOfNode(location); - if (file) { - if (isJsxOpeningFragment(location)) { - if (file.localJsxFragmentNamespace) { - return file.localJsxFragmentNamespace; - } - const jsxFragmentPragma = file.pragmas.get("jsxfrag"); - if (jsxFragmentPragma) { - const chosenPragma = isArray(jsxFragmentPragma) ? jsxFragmentPragma[0] : jsxFragmentPragma; - file.localJsxFragmentFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); - visitNode(file.localJsxFragmentFactory, markAsSynthetic, isEntityName); - if (file.localJsxFragmentFactory) { - return file.localJsxFragmentNamespace = getFirstIdentifier(file.localJsxFragmentFactory).escapedText; - } - } - const entity = getJsxFragmentFactoryEntity(location); - if (entity) { - file.localJsxFragmentFactory = entity; - return file.localJsxFragmentNamespace = getFirstIdentifier(entity).escapedText; - } - } - else { - const localJsxNamespace = getLocalJsxNamespace(file); - if (localJsxNamespace) { - return file.localJsxNamespace = localJsxNamespace; - } - } - } - } - if (!_jsxNamespace) { - _jsxNamespace = "React" as __String; - if (compilerOptions.jsxFactory) { - _jsxFactoryEntity = parseIsolatedEntityName(compilerOptions.jsxFactory, languageVersion); - visitNode(_jsxFactoryEntity, markAsSynthetic); - if (_jsxFactoryEntity) { - _jsxNamespace = getFirstIdentifier(_jsxFactoryEntity).escapedText; - } - } - else if (compilerOptions.reactNamespace) { - _jsxNamespace = escapeLeadingUnderscores(compilerOptions.reactNamespace); - } - } - if (!_jsxFactoryEntity) { - _jsxFactoryEntity = factory.createQualifiedName(factory.createIdentifier(unescapeLeadingUnderscores(_jsxNamespace)), "createElement"); - } - return _jsxNamespace; - } - - function getLocalJsxNamespace(file: SourceFile): __String | undefined { - if (file.localJsxNamespace) { - return file.localJsxNamespace; - } - const jsxPragma = file.pragmas.get("jsx"); - if (jsxPragma) { - const chosenPragma = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma; - file.localJsxFactory = parseIsolatedEntityName(chosenPragma.arguments.factory, languageVersion); - visitNode(file.localJsxFactory, markAsSynthetic, isEntityName); - if (file.localJsxFactory) { - return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText; - } - } - } - - function markAsSynthetic(node: T): VisitResult { - setTextRangePosEnd(node, -1, -1); - return 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, ...args: DiagnosticArguments): Diagnostic { - const diagnostic = location - ? createDiagnosticForNode(location, message, ...args) - : createCompilerDiagnostic(message, ...args); - const existing = diagnostics.lookup(diagnostic); - if (existing) { - return existing; - } - else { - diagnostics.add(diagnostic); - return diagnostic; - } - } - - function errorSkippedOn(key: keyof CompilerOptions, location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { - const diagnostic = error(location, message, ...args); - diagnostic.skippedOn = key; - return diagnostic; - } - - function createError(location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { - return location - ? createDiagnosticForNode(location, message, ...args) - : createCompilerDiagnostic(message, ...args); - } - - function error(location: Node | undefined, message: DiagnosticMessage, ...args: DiagnosticArguments): Diagnostic { - const diagnostic = createError(location, message, ...args); - diagnostics.add(diagnostic); - return diagnostic; - } - - function addErrorOrSuggestion(isError: boolean, diagnostic: Diagnostic) { - if (isError) { - diagnostics.add(diagnostic); - } - else { - suggestionDiagnostics.add({ ...diagnostic, category: DiagnosticCategory.Suggestion }); - } - } - function errorOrSuggestion(isError: boolean, location: Node, message: DiagnosticMessage | DiagnosticMessageChain, ...args: DiagnosticArguments): void { - // Pseudo-synthesized input node - if (location.pos < 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, ...args) : createDiagnosticForFileFromMessageChain(file, message)); // eslint-disable-line local/no-in-operator - return; - } - addErrorOrSuggestion(isError, "message" in message ? createDiagnosticForNode(location, message, ...args) : createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(location), location, message)); // eslint-disable-line local/no-in-operator - } - - function errorAndMaybeSuggestAwait( - location: Node, - maybeMissingAwait: boolean, - message: DiagnosticMessage, - ...args: DiagnosticArguments): Diagnostic { - const diagnostic = error(location, message, ...args); - if (maybeMissingAwait) { - const related = createDiagnosticForNode(location, Diagnostics.Did_you_forget_to_use_await); - addRelatedInfo(diagnostic, related); - } - return diagnostic; - } - - function addDeprecatedSuggestionWorker(declarations: Node | Node[], diagnostic: DiagnosticWithLocation) { - const deprecatedTag = Array.isArray(declarations) ? forEach(declarations, getJSDocDeprecatedTag) : getJSDocDeprecatedTag(declarations); - if (deprecatedTag) { - addRelatedInfo( - diagnostic, - createDiagnosticForNode(deprecatedTag, Diagnostics.The_declaration_was_marked_as_deprecated_here) - ); - } - // We call `addRelatedInfo()` before adding the diagnostic to prevent duplicates. - suggestionDiagnostics.add(diagnostic); - return diagnostic; - } - - function isDeprecatedSymbol(symbol: Symbol) { - if (length(symbol.declarations) > 1) { - const parentSymbol = getParentOfSymbol(symbol); - if (parentSymbol && parentSymbol.flags & SymbolFlags.Interface) { - return some(symbol.declarations, d => !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated)); - } - } - return !!(getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Deprecated); - } - - function addDeprecatedSuggestion(location: Node, declarations: Node[], deprecatedEntity: string) { - const diagnostic = createDiagnosticForNode(location, Diagnostics._0_is_deprecated, deprecatedEntity); - return addDeprecatedSuggestionWorker(declarations, diagnostic); - } - - function addDeprecatedSuggestionWithSignature(location: Node, declaration: Node, deprecatedEntity: string | undefined, signatureString: string) { - const diagnostic = deprecatedEntity - ? createDiagnosticForNode(location, Diagnostics.The_signature_0_of_1_is_deprecated, signatureString, deprecatedEntity) - : createDiagnosticForNode(location, Diagnostics._0_is_deprecated, signatureString); - return addDeprecatedSuggestionWorker(declaration, diagnostic); - } - - function createSymbol(flags: SymbolFlags, name: __String, checkFlags?: CheckFlags) { - symbolCount++; - const symbol = new Symbol(flags | SymbolFlags.Transient, name) as TransientSymbol; - symbol.links = new SymbolLinks() as TransientSymbolLinks; - symbol.links.checkFlags = checkFlags || CheckFlags.None; - return symbol; - } - - function createParameter(name: __String, type: Type) { - const symbol = createSymbol(SymbolFlags.FunctionScopedVariable, name); - symbol.links.type = type; - return symbol; - } - - function createProperty(name: __String, type: Type) { - const symbol = createSymbol(SymbolFlags.Property, name); - symbol.links.type = type; - return symbol; - } - - function getExcludedSymbolFlags(flags: SymbolFlags): SymbolFlags { - let result: SymbolFlags = 0; - if (flags & SymbolFlags.BlockScopedVariable) result |= SymbolFlags.BlockScopedVariableExcludes; - if (flags & SymbolFlags.FunctionScopedVariable) result |= SymbolFlags.FunctionScopedVariableExcludes; - if (flags & SymbolFlags.Property) result |= SymbolFlags.PropertyExcludes; - if (flags & SymbolFlags.EnumMember) result |= SymbolFlags.EnumMemberExcludes; - if (flags & SymbolFlags.Function) result |= SymbolFlags.FunctionExcludes; - if (flags & SymbolFlags.Class) result |= SymbolFlags.ClassExcludes; - if (flags & SymbolFlags.Interface) result |= SymbolFlags.InterfaceExcludes; - if (flags & SymbolFlags.RegularEnum) result |= SymbolFlags.RegularEnumExcludes; - if (flags & SymbolFlags.ConstEnum) result |= SymbolFlags.ConstEnumExcludes; - if (flags & SymbolFlags.ValueModule) result |= SymbolFlags.ValueModuleExcludes; - if (flags & SymbolFlags.Method) result |= SymbolFlags.MethodExcludes; - if (flags & SymbolFlags.GetAccessor) result |= SymbolFlags.GetAccessorExcludes; - if (flags & SymbolFlags.SetAccessor) result |= SymbolFlags.SetAccessorExcludes; - if (flags & SymbolFlags.TypeParameter) result |= SymbolFlags.TypeParameterExcludes; - if (flags & SymbolFlags.TypeAlias) result |= SymbolFlags.TypeAliasExcludes; - if (flags & SymbolFlags.Alias) result |= SymbolFlags.AliasExcludes; - return result; - } - - function recordMergedSymbol(target: Symbol, source: Symbol) { - if (!source.mergeId) { - source.mergeId = nextMergeId; - nextMergeId++; - } - mergedSymbols[source.mergeId] = target; - } - - function cloneSymbol(symbol: Symbol): TransientSymbol { - const result = createSymbol(symbol.flags, symbol.escapedName); - result.declarations = symbol.declarations ? symbol.declarations.slice() : []; - result.parent = symbol.parent; - if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; - if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; - if (symbol.members) result.members = new Map(symbol.members); - if (symbol.exports) result.exports = new Map(symbol.exports); - recordMergedSymbol(result, symbol); - return result; - } - - /** - * Note: if target is transient, then it is mutable, and mergeSymbol with both mutate and return it. - * If target is not transient, mergeSymbol will produce a transient clone, mutate that and return it. - */ - function mergeSymbol(target: Symbol, source: Symbol, unidirectional = false): Symbol { - if (!(target.flags & getExcludedSymbolFlags(source.flags)) || - (source.flags | target.flags) & SymbolFlags.Assignment) { - if (source === target) { - // This can happen when an export assigned namespace exports something also erroneously exported at the top level - // See `declarationFileNoCrashOnExtraExportModifier` for an example - return target; - } - if (!(target.flags & SymbolFlags.Transient)) { - const resolvedTarget = resolveSymbol(target); - if (resolvedTarget === unknownSymbol) { - return source; - } - 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( - source.declarations && getNameOfDeclaration(source.declarations[0]), - Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, - symbolToString(target)); - } - } - else { // 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 isSourcePlainJs = isPlainJsFile(sourceSymbolFile, compilerOptions.checkJs); - const isTargetPlainJs = isPlainJsFile(targetSymbolFile, compilerOptions.checkJs); - const symbolName = symbolToString(source); - - // Collect top-level duplicate identifier errors into one mapping, so we can then merge their diagnostics if there are a bunch - if (sourceSymbolFile && targetSymbolFile && amalgamatedDuplicates && !isEitherEnum && sourceSymbolFile !== targetSymbolFile) { - const firstFile = comparePaths(sourceSymbolFile.path, targetSymbolFile.path) === Comparison.LessThan ? sourceSymbolFile : targetSymbolFile; - const secondFile = firstFile === sourceSymbolFile ? targetSymbolFile : sourceSymbolFile; - const filesDuplicates = getOrUpdate(amalgamatedDuplicates, `${firstFile.path}|${secondFile.path}`, (): DuplicateInfoForFiles => - ({ firstFile, secondFile, conflictingSymbols: new Map() })); - const conflictingSymbolInfo = getOrUpdate(filesDuplicates.conflictingSymbols, symbolName, (): DuplicateInfoForSymbol => - ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] })); - if (!isSourcePlainJs) addDuplicateLocations(conflictingSymbolInfo.firstFileLocations, source); - if (!isTargetPlainJs) addDuplicateLocations(conflictingSymbolInfo.secondFileLocations, target); - } - else { - if (!isSourcePlainJs) addDuplicateDeclarationErrorsForSymbols(source, message, symbolName, target); - if (!isTargetPlainJs) addDuplicateDeclarationErrorsForSymbols(target, message, symbolName, source); - } - } - return target; - - function addDuplicateLocations(locs: Declaration[], symbol: Symbol): void { - if (symbol.declarations) { - for (const decl of symbol.declarations) { - pushIfUnique(locs, decl); - } - } - } - } - - function addDuplicateDeclarationErrorsForSymbols(target: Symbol, message: DiagnosticMessage, symbolName: string, source: Symbol) { - forEach(target.declarations, node => { - addDuplicateDeclarationError(node, message, symbolName, source.declarations); - }); - } - - function addDuplicateDeclarationError(node: Declaration, message: DiagnosticMessage, symbolName: string, relatedNodes: readonly Declaration[] | undefined) { - const errorNode = (getExpandoInitializer(node, /*isPrototypeAssignment*/ false) ? getNameOfExpando(node) : getNameOfDeclaration(node)) || node; - const err = lookupOrIssueError(errorNode, message, symbolName); - for (const relatedNode of relatedNodes || emptyArray) { - const adjustedNode = (getExpandoInitializer(relatedNode, /*isPrototypeAssignment*/ false) ? getNameOfExpando(relatedNode) : getNameOfDeclaration(relatedNode)) || relatedNode; - if (adjustedNode === errorNode) continue; - err.relatedInformation = err.relatedInformation || []; - const leadingMessage = createDiagnosticForNode(adjustedNode, Diagnostics._0_was_also_declared_here, symbolName); - const followOnMessage = createDiagnosticForNode(adjustedNode, Diagnostics.and_here); - if (length(err.relatedInformation) >= 5 || some(err.relatedInformation, r => compareDiagnostics(r, followOnMessage) === Comparison.EqualTo || compareDiagnostics(r, leadingMessage) === Comparison.EqualTo)) continue; - addRelatedInfo(err, !length(err.relatedInformation) ? leadingMessage : followOnMessage); - } - } - - function combineSymbolTables(first: SymbolTable | undefined, second: SymbolTable | undefined): SymbolTable | undefined { - if (!first?.size) return second; - if (!second?.size) return first; - const combined = createSymbolTable(); - mergeSymbolTable(combined, first); - mergeSymbolTable(combined, second); - return combined; - } - - function mergeSymbolTable(target: SymbolTable, source: SymbolTable, unidirectional = false) { - source.forEach((sourceSymbol, id) => { - const targetSymbol = target.get(id); - target.set(id, targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : getMergedSymbol(sourceSymbol)); - }); - } - - function mergeModuleAugmentation(moduleName: StringLiteral | Identifier): void { - const moduleAugmentation = moduleName.parent as ModuleDeclaration; - if (moduleAugmentation.symbol.declarations?.[0] !== moduleAugmentation) { - // this is a combined symbol for multiple augmentations within the same file. - // its symbol already has accumulated information for all declarations - // so we need to add it just once - do the work only for first declaration - Debug.assert(moduleAugmentation.symbol.declarations!.length > 1); - return; - } - - if (isGlobalScopeAugmentation(moduleAugmentation)) { - mergeSymbolTable(globals, moduleAugmentation.symbol.exports!); - } - else { - // find a module that about to be augmented - // do not validate names of augmentations that are defined in ambient context - const moduleNotFoundError = !(moduleName.parent.parent.flags & NodeFlags.Ambient) - ? Diagnostics.Invalid_module_name_in_augmentation_module_0_cannot_be_found - : undefined; - let mainModule = resolveExternalModuleNameWorker(moduleName, moduleName, moduleNotFoundError, /*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 as TransientSymbol).links; - const id = getSymbolId(symbol); - return symbolLinks[id] ??= new SymbolLinks(); - } - - function getNodeLinks(node: Node): NodeLinks { - const nodeId = getNodeId(node); - return nodeLinks[nodeId] || (nodeLinks[nodeId] = new (NodeLinks as any)()); - } - - function isGlobalSourceFile(node: Node) { - return node.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(node as SourceFile); - } - - 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 targetFlags = getAllSymbolFlags(symbol); - // `targetFlags` will be `SymbolFlags.All` if an error occurred in alias resolution; this avoids cascading errors - if (targetFlags & meaning) { - return symbol; - } - } - } - } - // return undefined if we can't find a symbol. - } - - /** - * Get symbols that represent parameter-property-declaration as parameter and as property declaration - * @param parameter a parameterDeclaration node - * @param parameterName a name of the parameter to get the symbols for. - * @return a tuple of two symbols - */ - function getSymbolsOfParameterPropertyDeclaration(parameter: ParameterPropertyDeclaration, parameterName: __String): [Symbol, Symbol] { - const constructorDeclaration = parameter.parent; - const classDeclaration = parameter.parent.parent; - - const parameterSymbol = getSymbol(constructorDeclaration.locals!, parameterName, SymbolFlags.Value); - const propertySymbol = getSymbol(getMembersOfSymbol(classDeclaration.symbol), parameterName, SymbolFlags.Value); - - if (parameterSymbol && propertySymbol) { - return [parameterSymbol, propertySymbol]; - } - - return Debug.fail("There should exist two symbols, one as property declaration and one as parameter declaration"); - } - - function isBlockScopedNameDeclaredBeforeUse(declaration: Declaration, usage: Node): boolean { - const declarationFile = getSourceFileOfNode(declaration); - const useFile = getSourceFileOfNode(usage); - const declContainer = getEnclosingBlockScopeContainer(declaration); - if (declarationFile !== useFile) { - if ((moduleKind && (declarationFile.externalModuleIndicator || useFile.externalModuleIndicator)) || - (!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 !(getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && 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) || isInAmbientOrTypeNode(usage)) { - return true; - } - if (isUsedInFunctionOrInstanceProperty(usage, declaration)) { - if (getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields - && getContainingClass(declaration) - && (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent))) { - return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true); - } - else { - return true; - } - } - return false; - - function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean { - switch (declaration.parent.parent.kind) { - case SyntaxKind.VariableStatement: - case SyntaxKind.ForStatement: - case SyntaxKind.ForOfStatement: - // variable statement/for/for-of statement case, - // use site should not be inside variable declaration (initializer of declaration or binding element) - if (isSameScopeDescendentOf(usage, declaration, declContainer)) { - return true; - } - break; - } - - // ForIn/ForOf case - use site should not be used in expression part - const grandparent = declaration.parent.parent; - return isForInOrOfStatement(grandparent) && isSameScopeDescendentOf(usage, grandparent.expression, declContainer); - } - - function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node): boolean { - return !!findAncestor(usage, current => { - if (current === declContainer) { - return "quit"; - } - if (isFunctionLike(current)) { - return true; - } - if (isClassStaticBlockDeclaration(current)) { - return declaration.pos < usage.pos; - } - - const propertyDeclaration = tryCast(current.parent, isPropertyDeclaration); - if (propertyDeclaration) { - const initializerOfProperty = propertyDeclaration.initializer === current; - if (initializerOfProperty) { - if (isStatic(current.parent)) { - if (declaration.kind === SyntaxKind.MethodDeclaration) { - return true; - } - if (isPropertyDeclaration(declaration) && getContainingClass(usage) === getContainingClass(declaration)) { - const propName = declaration.name; - if (isIdentifier(propName) || isPrivateIdentifier(propName)) { - const type = getTypeOfSymbol(getSymbolOfDeclaration(declaration)); - const staticBlocks = filter(declaration.parent.members, isClassStaticBlockDeclaration); - if (isPropertyInitializedInStaticBlocks(propName, type, staticBlocks, declaration.parent.pos, current.pos)) { - return true; - } - } - } - } - else { - const isDeclarationInstanceProperty = declaration.kind === SyntaxKind.PropertyDeclaration && !isStatic(declaration); - 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 as FunctionLikeDeclaration; - if (isParameter(lastLocation) - && functionLocation.body - && result.valueDeclaration - && 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 || !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; - } - } - } - - function isConstAssertion(location: Node) { - return (isAssertionExpression(location) && isConstTypeReference(location.type)) - || (isJSDocTypeTag(location) && isConstTypeReference(location.typeExpression)); - } - - /** - * 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 nameNotFoundMessage If defined, we will report errors found during resolve. - * @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, - getSpellingSuggestions = true): Symbol | undefined { - return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSpellingSuggestions, getSymbol, true); - } + /** + * 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 nameNotFoundMessage If defined, we will report errors found during resolve. + * @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, + getSpellingSuggestions = true): Symbol | undefined { + return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSpellingSuggestions, getSymbol, true); + } function resolveNameHelper( location: Node | undefined, @@ -4020,7 +3345,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { result = undefined; } } - // TSPLUS EXTENSION START + // + // TSPLUS START + // else { if (originalLocation && originalLocation.parent && (isPropertyAccessExpression(originalLocation.parent) || isCallExpression(originalLocation.parent))) { const symbol = location.locals.get(name) @@ -4047,7 +3374,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } } - // TSPLUS EXTENSION END + // + // TSPLUS END + // } withinDeferredContext = withinDeferredContext || getIsDeferredContext(location, lastLocation); switch (location.kind) { @@ -4298,7 +3627,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { location.parent; } - // TSPLUS EXTENSION START + // + // TSPLUS START + // if (!result && originalLocation && checkTsPlusGlobals) { const globalImport = tsPlusGlobalImportCache.get(name as string); if (globalImport) { @@ -4336,7 +3667,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getSymbolLinks(globalSymbol).isPossibleCompanionReference = true; } } - // TSPLUS EXTENSION END + // + // TSPLUS END + // // 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. @@ -5742,9 +5075,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { markSymbolOfAliasDeclarationIfTypeOnly(getAliasDeclarationFromName(name), symbol, /*finalTarget*/ undefined, /*overwriteEmpty*/ true); } return (symbol.flags & meaning) || dontResolveAlias || - // TSPLUS EXTENSION BEGIN + // + // TSPLUS START + // getSymbolLinks(symbol).isPossibleCompanionReference - // TSPLUS EXTENSION END + // + // TSPLUS END + // ? symbol : resolveAlias(symbol); } @@ -7249,39 +6586,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return !!type.symbol && !!(type.symbol.flags & SymbolFlags.Class) && (type === getDeclaredTypeOfClassOrInterface(type.symbol) || (!!(type.flags & TypeFlags.Object) && !!(getObjectFlags(type) & ObjectFlags.IsClassInstanceClone))); } - // TSPLUS EXTENSION START - function isCompanionReference(node: Expression | QualifiedName): boolean { - let type: Type | undefined - - const symbol = getSymbolAtLocation(node); - if (symbol) { - type = getTypeOfSymbol(symbol); - } - else { - type = getNodeLinks(node).tsPlusResolvedType; - } - - if (!type) { - return false - } - - // Class companion object - if (getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class) { - return true - } - - // Synthetic Interface or TypeAlias companion object - if (symbol && symbol.declarations && symbol.declarations.length > 0) { - const declaration = symbol.declarations[0] - if (isInterfaceDeclaration(declaration) || isTypeAliasDeclaration(declaration)) { - return !!getSymbolLinks(symbol).isPossibleCompanionReference; - } - } - - return false - } - // TSPLUS EXTENSION END - function createNodeBuilder() { return { typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => @@ -17446,161 +16750,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; } - // TSPLUS EXTENSION START - function checkTsPlusCustomCall( - declaration: Declaration, - errorNode: Node, - args: Expression[], - checkMode: CheckMode | undefined, - signature?: Signature, - addDiagnostic?: (_: Diagnostic) => void, - ): Type { - const funcType = getTypeOfNode(declaration); - const apparentType = getApparentType(funcType); - const candidate = signature ?? getSignaturesOfType(apparentType, SignatureKind.Call)[0]!; - if (!candidate) { - return errorType; - } - const node = factory.createCallExpression( - factory.createIdentifier("$tsplus_custom_call"), - [], - args - ); - setTextRange(node, errorNode) - setParent(node, declaration.parent); - if (candidate.typeParameters) { - const inferenceContext = createInferenceContext( - candidate.typeParameters, - candidate, - InferenceFlags.None - ); - const typeArgumentTypes = inferTypeArguments( - node, - candidate, - args, - checkMode || CheckMode.Normal, - inferenceContext - ); - const signature = getSignatureInstantiation( - candidate, - typeArgumentTypes, - /*isJavascript*/ false, - inferenceContext && inferenceContext.inferredTypeParameters - ); - const digs = getSignatureApplicabilityError( - node, - args, - signature, - assignableRelation, - checkMode || CheckMode.Normal, - /*reportErrors*/ addDiagnostic ? true : false, - /*containingMessageChain*/ void 0 - ); - if (digs) { - digs.forEach((dig) => { - addDiagnostic?.(dig); - }); - return errorType; - } - return getReturnTypeOfSignature(signature); - } - const digs = getSignatureApplicabilityError( - node, - args, - candidate, - assignableRelation, - CheckMode.Normal, - /*reportErrors*/ true, - /*containingMessageChain*/ void 0 - ); - if (digs) { - digs.forEach((dig) => { - addDiagnostic?.(dig); - }); - return errorType; - } - return getReturnTypeOfSignature(candidate); - } - function getInstantiatedTsPlusSignature( - declaration: Declaration, - args: Expression[], - checkMode: CheckMode | undefined, - ): Signature { - const funcType = getTypeOfNode(declaration); - const apparentType = getApparentType(funcType); - const candidate = getSignaturesOfType(apparentType, SignatureKind.Call)[0]!; - const node = factory.createCallExpression( - factory.createIdentifier("$tsplus_custom_call"), - [], - args - ); - setParent(node, declaration.parent); - if (candidate.typeParameters) { - const inferenceContext = createInferenceContext( - candidate.typeParameters, - candidate, - InferenceFlags.None - ); - const typeArgumentTypes = inferTypeArguments( - node, - candidate, - args, - checkMode || CheckMode.Normal, - inferenceContext - ); - const signature = getSignatureInstantiation( - candidate, - typeArgumentTypes, - /*isJavascript*/ false, - inferenceContext && inferenceContext.inferredTypeParameters - ); - return signature; - } - return candidate; - } - - function computeUnifiedType(unifier: FunctionDeclaration, union: Type) { - for (const signature of getSignaturesOfType(getTypeOfNode(unifier), SignatureKind.Call)) { - if (signature.minArgumentCount === 1 && signature.typeParameters) { - const context = createInferenceContext(signature.typeParameters, signature, InferenceFlags.None); - inferTypes(context.inferences, union, getTypeOfSymbol(signature.parameters[0])); - const instantiated = getSignatureInstantiation(signature, getInferredTypes(context), /*isJavascript*/ false) - if (isTypeAssignableTo(union, getTypeOfSymbol(instantiated.parameters[0]))) { - return getReturnTypeOfSignature(instantiated); - } - } - } - return errorType; - } - - function getUnifiedType(unionType: UnionType): Type { - if (unionType.tsPlusUnified) { - return unionType; - } - let type = unionType.types[0]; - while (type.flags & TypeFlags.Union) { - type = (type as UnionType).types[0]; - } - const targetSymbol = type.symbol || type.aliasSymbol; - if (targetSymbol) { - for (const declaration of (targetSymbol.declarations ?? [])) { - for (let target of collectTsPlusTypeTags(declaration)) { - const id = identityCache.get(target); - if (id) { - const unified = computeUnifiedType(id, unionType); - if ((unified.flags & TypeFlags.Union) && unionType.types.length < (unified as UnionType).types.length) { - return unionType; - } - return unified; - } - } - } - } - unionType.tsPlusUnified = true; - return unionType; - } - // TSPLUS EXTENSION END - // 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 @@ -27411,7 +26560,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const type = getTypeOfDottedName((node as PropertyAccessExpression).expression, diagnostic); if (type) { const name = (node as PropertyAccessExpression).name; - // TSPLUS EXTENSION BEGIN + // + // TSPLUS START + // // Since our fluent properties are "fake", to resolve a dotted name, we have to look up // the parent type in the cache const symbol = type.symbol || type.aliasSymbol; @@ -27429,7 +26580,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return staticCompanionExtension.type; } } - // TSPLUS EXTENSION END + // + // TSPLUS END + // let prop: Symbol | undefined; if (isPrivateIdentifier(name)) { if (!type.symbol) { @@ -29047,47 +28200,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type; } - // TSPLUS EXTENSION START - function tsPlusShouldMarkIdentifierAliasReferenced(node: Identifier, type: Type): boolean { - if (node.parent && isCallExpression(node.parent) && node === node.parent.expression && type.symbol && type.symbol.valueDeclaration) { - if (collectTsPlusMacroTags(type.symbol.valueDeclaration).find((tag) => tag === "pipe")) { - return false; - } - } - if ( - !(node.parent && - isCallExpression(node.parent) && - node.parent.expression === node && - getSignaturesOfType(type, SignatureKind.Call).length === 0 && - ( - (getStaticExtension(type, "__call") != null) || - (getStaticCompanionExtension(type, "__call") != null) - ) - ) && - !isTransformablePipeableExtension(type) - ) { - return true; - } - return false; - } - function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type { - if (isThisInTypeQuery(node)) { - return checkThisExpression(node); - } - - const type = checkIdentifierOriginal(node, checkMode); - // 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)) { - // We should also not mark as used identifiers that will be replaced - if (tsPlusShouldMarkIdentifierAliasReferenced(node, type)) { - markAliasReferenced(getResolvedSymbol(node), node); - } - } - return type; - } - // TSPLUS EXTENSION END - function checkIdentifierOriginal(node: Identifier, checkMode: CheckMode | undefined): Type { if (isThisInTypeQuery(node)) { return checkThisExpression(node); @@ -32664,82 +31776,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { && getThisContainer(node, /*includeArrowFunctions*/ true, /*includeClassComputedPropertyName*/ false) === getDeclaringConstructor(prop); } - // TSPLUS EXTENSION START - function checkPropertyAccessForExtension(node: PropertyAccessExpression | QualifiedName, _left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier, _checkMode: CheckMode | undefined) { - const inType = getPropertiesOfType(leftType).findIndex((p) => p.escapedName === right.escapedText) !== -1; - if (!inType) { - const nodeLinks = getNodeLinks(node); - if (nodeLinks.tsPlusResolvedType) { - return nodeLinks.tsPlusResolvedType; - } - if (isCompanionReference(_left)) { - const companionExt = getStaticCompanionExtension(leftType, right.escapedText.toString()); - if (companionExt) { - nodeLinks.tsPlusStaticExtension = companionExt; - nodeLinks.tsPlusResolvedType = companionExt.type - return companionExt.type; - } - } - const fluentExtType = getFluentExtension(leftType, right.escapedText.toString()); - if (fluentExtType && isCallExpression(node.parent) && node.parent.expression === node) { - if (isExtensionValidForTarget(fluentExtType, leftType)) { - if (isIdentifier(_left)) { - markAliasReferenced(getResolvedSymbol(_left), _left); - } - nodeLinks.tsPlusResolvedType = fluentExtType; - nodeLinks.isFluent = true; - return fluentExtType; - } - return; - } - const getterExt = getGetterExtension(leftType, right.escapedText.toString()) - if (getterExt && isExpression(_left)) { - const symbol = getterExt.patched(_left); - if (symbol) { - if (isIdentifier(_left)) { - markAliasReferenced(getResolvedSymbol(_left), _left); - } - const type = getTypeOfSymbol(symbol); - nodeLinks.tsPlusGetterExtension = getterExt; - nodeLinks.tsPlusResolvedType = type; - nodeLinks.tsPlusSymbol = symbol; - return type; - } - } - const staticExt = getStaticExtension(leftType, right.escapedText.toString()); - if (staticExt) { - nodeLinks.tsPlusStaticExtension = staticExt; - nodeLinks.tsPlusResolvedType = staticExt.type; - return staticExt.type; - } - } - } - function checkFluentPipeableAgreement(pipeableExtension: TsPlusPipeableExtension) { - if (!fluentCache.has(pipeableExtension.typeName)) { - return; - } - const fluentMap = fluentCache.get(pipeableExtension.typeName)!; - if (!fluentMap.has(pipeableExtension.funcName)) { - return; - } - const fluentExtension = fluentMap.get(pipeableExtension.funcName)!(); - if (!fluentExtension || some(fluentExtension.types, ({ type: fluentType }) => isTypeAssignableTo(fluentType, pipeableExtension.getTypeAndSignatures()[0]))) { - return; - } - else { - error(pipeableExtension.declaration, Diagnostics.Declaration_annotated_as_pipeable_is_not_assignable_to_its_corresponding_fluent_declaration); - return; - } - } - // TSPLUS EXTENSION END - function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier, checkMode: CheckMode | undefined, writeOnly?: boolean) { - // TSPLUS EXTENSION START - const forExtension = checkPropertyAccessForExtension(node, left, leftType, right, checkMode); - if (forExtension) { - return forExtension; + // + // TSPLUS START + // + const tsPlusExtension = checkPropertyAccessForExtension(node, left, leftType, right, checkMode); + if (tsPlusExtension) { + return tsPlusExtension; } - // TSPLUS EXTENSION END + // + // TSPLUS END + // const parentSymbol = getNodeLinks(left).resolvedSymbol; const assignmentKind = getAssignmentTargetKind(node); const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType); @@ -32802,7 +31849,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { !(prop && (isConstEnumOrConstEnumOnlyModule(prop) || prop.flags & SymbolFlags.EnumMember && node.parent.kind === SyntaxKind.EnumMember)) || shouldPreserveConstEnums(compilerOptions) && isExportOrExportExpression(node) )) { - // TSPLUS EXTENSION START + // + // TSPLUS START + // if (isIdentifier(right) && prop) { const propLinks = getSymbolLinks(prop); if (propLinks.type) { @@ -32817,7 +31866,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { else { markAliasReferenced(parentSymbol, node); } - // TSPLUS EXTENSION END + // + // TSPLUS END + // } let propType: Type; @@ -33441,7 +32492,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return errorType; } - // TSPLUS EXTENSION BEGIN + // + // TSPLUS START + // const symbols = collectRelevantSymbols(objectType) const tags = symbols.flatMap((symbol) => { const tag = typeSymbolCache.get(symbol) @@ -33470,7 +32523,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return res; } } - // TSPLUS EXTENSION END + // + // TSPLUS END + // const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType; const accessFlags = isAssignmentTarget(node) ? @@ -33794,19 +32849,27 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const thisType = getThisTypeOfSignature(signature); if (thisType && couldContainTypeVariables(thisType)) { - // TSPLUS EXTENTION BEGIN + // + // TSPLUS START + // const paramType = unionIfLazy(thisType); const thisArgumentNode = getThisArgumentOfCall(node); inferTypes(context.inferences, getThisArgumentType(thisArgumentNode), paramType); - // TSPLUS EXTENTION END + // + // TSPLUS END + // } for (let i = 0; i < argCount; i++) { const arg = args[i]; if (arg.kind !== SyntaxKind.OmittedExpression && !(checkMode & CheckMode.IsForStringLiteralArgumentCompletions && hasSkipDirectInferenceFlag(arg))) { - // TSPLUS EXTENTION BEGIN + // + // TSPLUS START + // const paramType = unionIfLazy(getTypeAtPosition(signature, i)); - // TSPLUS EXTENTION END + // + // TSPLUS END + // if (couldContainTypeVariables(paramType)) { const argType = checkExpressionWithContextualType(arg, paramType, context, checkMode); inferTypes(context.inferences, argType, paramType); @@ -33822,18 +32885,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getInferredTypes(context); } - // TSPLUS EXTENSION BEGIN - function isLazyParameterByType(type: Type): type is TypeReference { - if (type.symbol && type.symbol.declarations && type.symbol.declarations.length > 0) { - const tag = collectTsPlusTypeTags(type.symbol.declarations[0])[0]; - if (tag === "tsplus/LazyArgument") { - return true; - } - } - return false; - } - // TSPLUS EXTENSION END - function getMutableArrayOrTupleType(type: Type) { return type.flags & TypeFlags.Union ? mapType(type, getMutableArrayOrTupleType) : type.flags & TypeFlags.Any || isMutableArrayOrTuple(getBaseConstraintOfType(type) || type) ? type : @@ -34772,7 +33823,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { continue; } } - // TSPLUS EXTENTION BEGIN + // + // TSPLUS START + // // Only derive parameters once all type parameters are inferred for (let i = args.length; i < candidate.parameters.length; i++) { const param = candidate.parameters[i]; @@ -34786,7 +33839,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { deriveParameter(node, paramType, i); } } - // TSPLUS EXTENSION END + // + // TSPLUS END + // candidates[candidateIndex] = checkCandidate; return checkCandidate; } @@ -34977,7 +34032,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // that the user will not add any. let callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); - // TSPLUS EXTENSION START + // + // TSPLUS START + // if (callSignatures.length === 0) { let callExtension: TsPlusStaticFunctionExtension | undefined if (isCompanionReference(node.expression)) { @@ -35008,7 +34065,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } } - // TSPLUS EXTENSION END + // + // TSPLUS END + // const numConstructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct).length; @@ -35686,17597 +34745,18583 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } - // TSPLUS EXTENSION START - function tryCacheOptimizedPipeableCall(node: CallExpression): void { - if (isIdentifier(node.expression)) { - const identifierType = getTypeOfNode(node.expression); - const identifierSymbol = identifierType.symbol; - if (identifierSymbol && isTsPlusSymbol(identifierSymbol)) { - if (identifierSymbol.tsPlusTag === TsPlusSymbolTag.PipeableIdentifier) { - const fluentExtension = checker.getFluentExtensionForPipeableSymbol(identifierSymbol); - if (fluentExtension) { - const signature = find(fluentExtension.types, ({ type }) => checker.isTypeAssignableTo(identifierSymbol.getTsPlusDataFirstType(), type))?.signatures[0]; - if (signature) { - getNodeLinks(node).tsPlusOptimizedDataFirst = { - definition: signature.tsPlusFile, - exportName: signature.tsPlusExportName - }; - } - } - } - if (identifierSymbol.tsPlusTag === TsPlusSymbolTag.PipeableMacro) { - getNodeLinks(node).tsPlusOptimizedDataFirst = { - definition: identifierSymbol.tsPlusSourceFile, - exportName: identifierSymbol.tsPlusExportName - }; - } - } + /** + * 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 checkCallExpressionOriginal(node: CallExpression | NewExpression, checkMode?: CheckMode): Type { + checkGrammarTypeArguments(node, node.typeArguments); + + 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 silentNeverType. + return silentNeverType; } - if (isPropertyAccessExpression(node.expression) && isIdentifier(node.expression.name)) { - const identifierType = checker.getTypeAtLocation(node.expression.name); - const identifierSymbol = identifierType.symbol; - if (identifierSymbol && isTsPlusSymbol(identifierSymbol)) { - if (identifierSymbol.tsPlusTag === TsPlusSymbolTag.PipeableIdentifier) { - const fluentExtension = checker.getFluentExtensionForPipeableSymbol(identifierSymbol); - if (fluentExtension) { - const signature = find(fluentExtension.types, ({ type }) => checker.isTypeAssignableTo(identifierSymbol.getTsPlusDataFirstType(), type))?.signatures[0]; - if (signature) { - getNodeLinks(node).tsPlusOptimizedDataFirst = { - definition: signature.tsPlusFile, - exportName: signature.tsPlusExportName - }; - } - } - } - if (identifierSymbol.tsPlusTag === TsPlusSymbolTag.PipeableMacro) { - getNodeLinks(node).tsPlusOptimizedDataFirst = { - definition: identifierSymbol.tsPlusSourceFile, - exportName: identifierSymbol.tsPlusExportName - }; - } - if (identifierSymbol.tsPlusTag === TsPlusSymbolTag.StaticFunction) { - const declType = checker.getTypeAtLocation(identifierSymbol.tsPlusDeclaration.name!); - const declSym = declType.symbol; - if (declSym && isTsPlusSymbol(declSym)) { - if (declSym.tsPlusTag === TsPlusSymbolTag.PipeableIdentifier) { - const fluentExtension = checker.getFluentExtensionForPipeableSymbol(declSym); - if (fluentExtension) { - const signature = find(fluentExtension.types, ({ type }) => checker.isTypeAssignableTo(declSym.getTsPlusDataFirstType(), type))?.signatures[0]; - if (signature) { - getNodeLinks(node).tsPlusOptimizedDataFirst = { - definition: signature.tsPlusFile, - exportName: signature.tsPlusExportName - }; - } - } - } - if (declSym.tsPlusTag === TsPlusSymbolTag.PipeableMacro) { - getNodeLinks(node).tsPlusOptimizedDataFirst = { - definition: declSym.tsPlusSourceFile, - exportName: declSym.tsPlusExportName - }; - } - } + + 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 && + !(isJSDocSignature(declaration) && getJSDocRoot(declaration)?.parent?.kind === SyntaxKind.Constructor) && + !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; } } - } - function getDerivationDebugDiagnostic(location: CallExpression, derivation: Derivation): Diagnostic { - const sourceFile = getSourceFileOfNode(location) - switch(derivation._tag) { - case "EmptyObjectDerivation": { - return createError( - location.arguments[0], - Diagnostics.Deriving_type_0_1, - typeToString(derivation.type), - "intrinsically as it is an empty object" - ) - } - case "InvalidDerivation": { - throw new Error("Bug, derivation debug called with InvalidDerivation") - } - case "FromBlockScope": { - return createError( - location.arguments[0], - Diagnostics.Deriving_type_0_1, - typeToString(derivation.type), - `using block-scoped implicit ${derivation.implicit.symbol.escapedName}` - ); - } - case "FromImplicitScope": { - return createError( - location.arguments[0], - Diagnostics.Deriving_type_0_1, - typeToString(derivation.type), - `using implicit ${ - getSourceFileOfNode(location).fileName !== getSourceFileOfNode(derivation.implicit).fileName ? - `${getImportPath(derivation.implicit)}#${derivation.implicit.symbol.escapedName}` : - derivation.implicit.symbol.escapedName - }` - ) - } - case "FromIntersectionStructure": { - return createDiagnosticForNodeFromMessageChain( - sourceFile, - location.arguments[0], - chainDiagnosticMessages( - derivation.fields.map((d) => createDiagnosticMessageChainFromDiagnostic(getDerivationDebugDiagnostic(location, d))), - Diagnostics.Deriving_type_0_1, - typeToString(derivation.type), - "intrinsically as an intersection" - ) - ) - } - case "FromObjectStructure": { - return createDiagnosticForNodeFromMessageChain( - sourceFile, - location.arguments[0], - chainDiagnosticMessages( - derivation.fields.map((d) => createDiagnosticMessageChainFromDiagnostic(getDerivationDebugDiagnostic(location, d.value))), - Diagnostics.Deriving_type_0_1, - typeToString(derivation.type), - "intrinsically as an object structure" - ) - ) - } - case "FromTupleStructure": { - return createDiagnosticForNodeFromMessageChain( - sourceFile, - location.arguments[0], - chainDiagnosticMessages( - derivation.fields.map((d) => createDiagnosticMessageChainFromDiagnostic(getDerivationDebugDiagnostic(location, d))), - Diagnostics.Deriving_type_0_1, - typeToString(derivation.type), - "intrinsically as a tuple structure" - ) - ) - } - case "FromLiteral": { - return createError( - location.arguments[0], - Diagnostics.Deriving_type_0_1, - typeToString(derivation.type), - `intrinsically as a literal ${typeof derivation.value}` - ) - } - case "FromPriorDerivation": { - return createError( - location.arguments[0], - Diagnostics.Deriving_type_0_1, - typeToString(derivation.type), - `from derivation scope` - ) + + // In JavaScript files, calls to any identifier 'require' are treated as external module imports + if (isInJSFile(node) && shouldResolveJsRequire(compilerOptions) && 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.questionDotToken && 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); } - case "FromRule": { - return createDiagnosticForNodeFromMessageChain( - sourceFile, - location.arguments[0], - chainDiagnosticMessages( - derivation.arguments.map((d) => createDiagnosticMessageChainFromDiagnostic(getDerivationDebugDiagnostic(location, d))), - Diagnostics.Deriving_type_0_1, - typeToString(derivation.type), - `using${(derivation.usedBy.length > 0 ? " (recursive)" : "")} rule ${ - getSourceFileOfNode(location).fileName !== getSourceFileOfNode(derivation.rule).fileName ? - `${getImportPath(derivation.rule)}#${derivation.rule.symbol.escapedName}` : - derivation.rule.symbol.escapedName - }` - ) - ) + 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); } - default: { - // @ts-expect-error - derivation._tag + } + + if (isInJSFile(node)) { + const jsSymbol = getSymbolOfExpando(node, /*allowDeclaration*/ false); + if (jsSymbol?.exports?.size) { + const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, emptyArray); + jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral; + return getIntersectionType([returnType, jsAssignmentType]); } - throw new Error("Bug, unreachable") } + + return returnType; } - function getImportPath(declaration: Declaration) { - let path: string | undefined; - const locationTag = getAllJSDocTags(declaration, (tag): tag is JSDocTag => tag.tagName.escapedText === "tsplus" && tag.comment?.toString().startsWith("location") === true)[0]; - if (locationTag) { - const match = locationTag.comment!.toString().match(/^location "(.*)"/); - if (match) { - path = match[1] - } + + function checkDeprecatedSignature(signature: Signature, node: CallLikeExpression) { + if (signature.declaration && signature.declaration.flags & NodeFlags.Deprecated) { + const suggestionNode = getDeprecatedSuggestionNode(node); + const name = tryGetPropertyAccessOrIdentifierToString(getInvokedExpression(node)); + addDeprecatedSuggestionWithSignature(suggestionNode, signature.declaration, name, signatureToString(signature)); } - if (!path) { - path = getImportLocation(fileMap.map, getSourceFileOfNode(declaration).fileName); + } + + function getDeprecatedSuggestionNode(node: Node): Node { + node = skipParentheses(node); + switch (node.kind) { + case SyntaxKind.CallExpression: + case SyntaxKind.Decorator: + case SyntaxKind.NewExpression: + return getDeprecatedSuggestionNode((node as Decorator | CallExpression | NewExpression).expression); + case SyntaxKind.TaggedTemplateExpression: + return getDeprecatedSuggestionNode((node as TaggedTemplateExpression).tag); + case SyntaxKind.JsxOpeningElement: + case SyntaxKind.JsxSelfClosingElement: + return getDeprecatedSuggestionNode((node as JsxOpeningLikeElement).tagName); + case SyntaxKind.ElementAccessExpression: + return (node as ElementAccessExpression).argumentExpression; + case SyntaxKind.PropertyAccessExpression: + return (node as PropertyAccessExpression).name; + case SyntaxKind.TypeReference: + const typeReference = node as TypeReferenceNode; + return isQualifiedName(typeReference.typeName) ? typeReference.typeName.right : typeReference; + default: + return node; } - return path; } - function deriveParameter(deriveCallNode: CallLikeExpression, type: Type, parameterIndex: number): Type { - const nodeLinks = getNodeLinks(deriveCallNode); - if (!nodeLinks.tsPlusParameterDerivations) { - nodeLinks.tsPlusParameterDerivations = new Map(); + + function isSymbolOrSymbolForCall(node: Node) { + if (!isCallExpression(node)) return false; + let left = node.expression; + if (isPropertyAccessExpression(left) && left.name.escapedText === "for") { + left = left.expression; } - if (!nodeLinks.tsPlusParameterDerivations!.has(parameterIndex)) { - const derivationDiagnostics: Diagnostic[] = []; - const derivation = deriveTypeWorker(deriveCallNode, type, type, derivationDiagnostics, [], [], []); - nodeLinks.tsPlusParameterDerivations!.set(parameterIndex, derivation); - if (isErrorType(derivation.type)) { - derivationDiagnostics.forEach((diagnostic) => { - diagnostics.add(diagnostic); - }) - return errorType; - } + if (!isIdentifier(left) || left.escapedText !== "Symbol") { + return false; } - return nodeLinks.tsPlusParameterDerivations.get(parameterIndex)!.type; - } - function deriveType(deriveCallNode: CallExpression, type: Type): Type { - const nodeLinks = getNodeLinks(deriveCallNode); - if (!nodeLinks.tsPlusDerivation) { - const derivationDiagnostics: Diagnostic[] = []; - const derivation = deriveTypeWorker(deriveCallNode, type, type, derivationDiagnostics, [], [], []); - nodeLinks.tsPlusDerivation = derivation; - if (isErrorType(derivation.type)) { - derivationDiagnostics.forEach((diagnostic) => { - diagnostics.add(diagnostic); - }) - return errorType; - } else if (deriveCallNode.arguments.length > 0) { - diagnostics.add(getDerivationDebugDiagnostic(deriveCallNode, nodeLinks.tsPlusDerivation)); - return errorType; - } + + // make sure `Symbol` is the global symbol + const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); + if (!globalESSymbol) { + return false; } - return nodeLinks.tsPlusDerivation.type; + + return globalESSymbol === resolveName(left, "Symbol" as __String, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); } - function getSelfExportStatement(location: Node) { - let current = location; - while (!isVariableStatement(current) && current.parent) { - current = current.parent; + + function checkImportCallExpression(node: ImportCall): Type { + // Check grammar of dynamic import + checkGrammarImportCallExpression(node); + + if (node.arguments.length === 0) { + return createPromiseReturnType(node, anyType); } - if (current.parent) { - return current; + + const specifier = node.arguments[0]; + const specifierType = checkExpressionCached(specifier); + const optionsType = node.arguments.length > 1 ? checkExpressionCached(node.arguments[1]) : undefined; + // Even though multiple arguments is grammatically incorrect, type-check extra arguments for completion + for (let i = 2; i < node.arguments.length; ++i) { + checkExpressionCached(node.arguments[i]); } - } - function isLocalImplicit(node: Node): boolean { - return getAllJSDocTags(node, (tag): tag is JSDocTag => tag.tagName.escapedText === "tsplus" && typeof tag.comment === 'string' && tag.comment === 'implicit local').length > 0; - } - function tsPlusFlattenTags(toFlat: string[][]): readonly string[] { - let strings: readonly string[] = toFlat[0]; - for (let i = 1; i < toFlat.length; i++) { - strings = flatMap(strings, (prefix) => toFlat[i].map((post) => prefix + "," + post)) + + 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)); } - return strings.map((s) => `[${s}]`); - } - function collectTypeTagsOfType(type: Type) { - return flatMap(collectRelevantSymbols(type), (s) => typeSymbolCache.get(s)); - } - function getTsPlusDerivationRules(symbol: Symbol, targetTypes: readonly Type[]) { - const mergedRules: { lazyRule: Declaration | undefined; rules: [Rule, number, Declaration, Set][]; } = { - lazyRule: void 0, - rules: [] + + if (optionsType) { + const importCallOptionsType = getGlobalImportCallOptionsType(/*reportErrors*/ true); + if (importCallOptionsType !== emptyObjectType) { + checkTypeAssignableTo(optionsType, getNullableType(importCallOptionsType, TypeFlags.Undefined), node.arguments[1]); + } } - const paramExtensions = tsPlusFlattenTags(map(targetTypes, (t) => ["_", ...collectTypeTagsOfType(t)])); - const dedupe = new Set(); - forEach(map(symbol.declarations, collectTsPlusTypeTags), (tags) => { - forEach(tags, (tag) => { - collectForTag(tag); - paramExtensions.forEach((extended) => collectForTag(tag + extended)); - }); - }); - mergedRules.rules = mergedRules.rules.sort((a, b) => a[1] - b[1]); - return mergedRules; - function collectForTag(tag: string) { - const foundRules = tsPlusWorldScope.rules.get(tag); - if (foundRules) { - if (foundRules.lazyRule) { - mergedRules.lazyRule = foundRules.lazyRule; - } - foundRules.rules.forEach((rule) => { - if (!dedupe.has(rule[2])) { - dedupe.add(rule[2]); - mergedRules.rules.push(rule); - } - }); + + // resolveExternalModuleName will return undefined if the moduleReferenceExpression is not a string literal + const moduleSymbol = resolveExternalModuleName(node, specifier); + if (moduleSymbol) { + const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontResolveAlias*/ true, /*suppressInteropError*/ false); + if (esModuleSymbol) { + return createPromiseReturnType(node, + getTypeWithSyntheticDefaultOnly(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) || + getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) + ); + } + } + return createPromiseReturnType(node, anyType); + } + + function createDefaultPropertyWrapperForModule(symbol: Symbol, originalSymbol: Symbol | undefined, anonymousSymbol?: Symbol | undefined) { + const memberTable = createSymbolTable(); + const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); + newSymbol.parent = originalSymbol; + newSymbol.links.nameType = getStringLiteralType("default"); + newSymbol.links.aliasTarget = resolveSymbol(symbol); + memberTable.set(InternalSymbolName.Default, newSymbol); + return createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, emptyArray); + } + + function getTypeWithSyntheticDefaultOnly(type: Type, symbol: Symbol, originalSymbol: Symbol, moduleSpecifier: Expression) { + const hasDefaultOnly = isOnlyImportedAsDefault(moduleSpecifier); + if (hasDefaultOnly && type && !isErrorType(type)) { + const synthType = type as SyntheticDefaultModuleType; + if (!synthType.defaultOnlyType) { + const type = createDefaultPropertyWrapperForModule(symbol, originalSymbol); + synthType.defaultOnlyType = type; } + return synthType.defaultOnlyType; } + return undefined; } - function shouldTreatNominally(type: Type) { - if (type.symbol && type.symbol.declarations) { - for (const declaration of type.symbol.declarations) { - if ((isInterfaceDeclaration(declaration) || isClassDeclaration(declaration)) && declaration.tsPlusDeriveTags && declaration.tsPlusDeriveTags.length > 0) { - return true; + function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol, originalSymbol: Symbol, moduleSpecifier: Expression): Type { + if (allowSyntheticDefaultImports && type && !isErrorType(type)) { + const synthType = type as SyntheticDefaultModuleType; + if (!synthType.syntheticType) { + const file = originalSymbol.declarations?.find(isSourceFile); + const hasSyntheticDefault = canHaveSyntheticDefault(file, originalSymbol, /*dontResolveAlias*/ false, moduleSpecifier); + if (hasSyntheticDefault) { + const anonymousSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); + const defaultContainingObject = createDefaultPropertyWrapperForModule(symbol, originalSymbol, anonymousSymbol); + anonymousSymbol.links.type = defaultContainingObject; + synthType.syntheticType = isValidSpreadType(type) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*objectFlags*/ 0, /*readonly*/ false) : defaultContainingObject; + } + else { + synthType.syntheticType = type; } } + return synthType.syntheticType; } - return false; - } - - function getImplicitTags(type: Type): Set { - const tags = new Set(); - collectImplicitTagsWorker(type, tags, undefined); - return tags; + return type; } - function collectImplicitTagsWorker(type: Type, tags: Set, prefix: string | undefined) { - if (type.flags & TypeFlags.UnionOrIntersection) { - forEach((type as UnionOrIntersectionType).types, (d) => { - collectImplicitTagsWorker(d, tags, prefix); - }) + function isCommonJsRequire(node: Node): boolean { + if (!isRequireCall(node, /*requireStringLiteralLikeArgument*/ true)) { + return false; } - else if (shouldTreatNominally(type)) { - if ((getObjectFlags(type) & ObjectFlags.Reference)) { - const name = symbolName(type.symbol); - const args = getTypeArguments(type as TypeReference); - forEach(args, (arg) => { - collectImplicitTagsWorker(arg, tags, prefix ? `${prefix}.${name}` : name) - }) - } else { - tags.add(prefix ? `${prefix}.${symbolName(type.symbol)}` : symbolName(type.symbol)); - } + + // 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; } - else if (type.flags & (TypeFlags.Intrinsic | TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.TypeParameter)) { - tags.add(prefix ? `${prefix}.${typeToString(type)}` : typeToString(type)); + // project includes symbol named 'require' - make sure that it is ambient and local non-alias + if (resolvedRequire.flags & SymbolFlags.Alias) { + return false; } - else { - const props = getPropertiesOfType(type); - forEach(props, (prop) => { - tags.add(prefix ? `${prefix}.${symbolName(prop)}` : symbolName(prop)); - }) - if (props.length === 0) { - tags.add(prefix ? `${prefix}._` : `_`); - } + + 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 getTypeAndImplicitTags(symbol: Symbol) { - const links = getSymbolLinks(symbol); - if (!links.tsPlusTypeAndImplicitTags) { - const type = getTypeOfSymbol(symbol); - const tags = getImplicitTags(type); - links.tsPlusTypeAndImplicitTags = { - type, - tags - } + function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type { + if (!checkGrammarTaggedTemplateChain(node)) checkGrammarTypeArguments(node, node.typeArguments); + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject); } - return links.tsPlusTypeAndImplicitTags; + const signature = getResolvedSignature(node); + checkDeprecatedSignature(signature, node); + return getReturnTypeOfSignature(signature); } - function indexInScope(entry: Symbol) { - const { tags } = getTypeAndImplicitTags(entry); - forEach(arrayFrom(tags.values()), (tag) => { - let index = tsPlusWorldScope.implicits.get(tag); - if (!index) { - index = new Set(); - tsPlusWorldScope.implicits.set(tag, index); + function checkAssertion(node: AssertionExpression, checkMode: CheckMode | undefined) { + if (node.kind === SyntaxKind.TypeAssertionExpression) { + const file = getSourceFileOfNode(node); + if (file && fileExtensionIsOneOf(file.fileName, [Extension.Cts, Extension.Mts])) { + grammarErrorOnNode(node, Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Use_an_as_expression_instead); } - index.add(entry); - }); + } + return checkAssertionWorker(node, checkMode); } - function lookupInGlobalScope(location: Node, type: Type, selfExport: Node | undefined, tags: readonly string[]): Derivation | undefined { - let candidates: Set | undefined; - for (const tag of tags) { - const maybeBetter = tsPlusWorldScope.implicits.get(tag); - if (!maybeBetter) { - return - } - if (!candidates || maybeBetter.size < candidates.size) { - candidates = maybeBetter; - } - } - const candidatesIterator = candidates?.values(); - if (!candidatesIterator) { - return - } - let current = candidatesIterator.next(); - while (!current.done) { - const candidate = current.value; - if (tags.every((tag) => tsPlusWorldScope.implicits.get(tag)?.has(candidate) === true)) { - if (getSelfExportStatement(candidate.valueDeclaration!) !== selfExport && - isBlockScopedNameDeclaredBeforeUse(candidate.valueDeclaration!, location)) { - const { type: implicitType } = getTypeAndImplicitTags(candidate); - if (isTypeAssignableTo(implicitType, type)) { - return { - _tag: "FromImplicitScope", - type, - implicit: candidate.valueDeclaration! - }; - } - } - } - current = candidatesIterator.next(); + 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 as ParenthesizedExpression).expression); + case SyntaxKind.PrefixUnaryExpression: + const op = (node as PrefixUnaryExpression).operator; + const arg = (node as PrefixUnaryExpression).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 = skipParentheses((node as PropertyAccessExpression | ElementAccessExpression).expression); + const symbol = isEntityNameExpression(expr) ? resolveEntityName(expr, SymbolFlags.Value, /*ignoreErrors*/ true) : undefined; + return !!(symbol && symbol.flags & SymbolFlags.Enum); } + return false; } - function lookupInBlockScope(location: Node, type: Type, tags: readonly string[]): Derivation | undefined { - let container = getEnclosingBlockScopeContainer(location) as LocalsContainer; - while (container) { - if (container.locals) { - for (const local of arrayFrom(container.locals.values())) { - if (local.valueDeclaration && - (isParameterDeclaration(local.valueDeclaration as VariableLikeDeclaration) || isLocalImplicit(local.valueDeclaration)) && - isNamedDeclaration(local.valueDeclaration) && - isIdentifier(local.valueDeclaration.name) && - isBlockScopedNameDeclaredBeforeUse(local.valueDeclaration.name, location) - ) { - const { tags: implicitTags, type: implicitType } = getTypeAndImplicitTags(local); - if (tags.every((tag) => implicitTags.has(tag))) { - if (isTypeAssignableTo(implicitType, type)) { - local.valueDeclaration.symbol.isReferenced = SymbolFlags.Value; - const blockLinks = getNodeLinks(container); - if (!blockLinks.uniqueNames) { - blockLinks.uniqueNames = new Set() - } - const declaration = local.valueDeclaration as NamedDeclaration & { name: Identifier }; - blockLinks.uniqueNames.add(declaration); - const implicitLinks = getNodeLinks(local.valueDeclaration); - implicitLinks.needsUniqueNameInSope = true; - return { - _tag: "FromBlockScope", - type, - implicit: declaration - }; - } - } - } - } + function checkAssertionWorker(node: JSDocTypeAssertion | AssertionExpression, checkMode: CheckMode | undefined) { + const { type, expression } = getAssertionTypeAndExpression(node); + const 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); } - container = getEnclosingBlockScopeContainer(container) as LocalsContainer; + return getRegularTypeOfLiteralType(exprType); } + checkSourceElement(type); + checkNodeDeferred(node); + return getTypeFromTypeNode(type); } - function deriveTypeWorker( - location: Node, - originalType: Type, - type: Type, - diagnostics: Diagnostic[], - derivationScope: FromRule[], - prohibited: Type[], - currentDerivation: Type[] - ): Derivation { - const sourceFile = getSourceFileOfNode(location) - if (isTypeIdenticalTo(type, emptyObjectType)) { - return { - _tag: "EmptyObjectDerivation", - type - }; - } - for (const derivedType of derivationScope) { - if (isTypeAssignableTo(derivedType.type, type)) { - const rule: FromPriorDerivation = { - _tag: "FromPriorDerivation", - derivation: derivedType, - type - }; - derivedType.usedBy.push(rule); - return rule; - } - } - const tagsForLookup = arrayFrom(getImplicitTags(type).values()); - const inBlockdScope = lookupInBlockScope(location, type, tagsForLookup); - if (inBlockdScope) { - return inBlockdScope; - } - const selfExport = getSelfExportStatement(location); - const inWorldScope = lookupInGlobalScope(location, type, selfExport, tagsForLookup); - if (inWorldScope) { - return inWorldScope; - } - const newCurrentDerivation = [...currentDerivation, type]; - for (const prohibitedType of prohibited) { - if (isTypeIdenticalTo(prohibitedType, type)) { - if (diagnostics.length === 0) { - const digs: Diagnostic[] = []; - for (let i = 1; i < newCurrentDerivation.length - 1; i++) { - const step = newCurrentDerivation[i]; - digs.push(createError( - location, - Diagnostics.Failed_derivation_of_type_0, - typeToString(step) - )); - } - const diagnostic = createDiagnosticForNodeFromMessageChain( - sourceFile, - location, - chainDiagnosticMessages( - map(digs, createDiagnosticMessageChainFromDiagnostic), - Diagnostics.Cannot_derive_type_0_the_derivation_requires_a_lazy_rule_to_be_in_scope_as_it_is_cyclic_for_the_type_1, - typeToString(newCurrentDerivation[0]), - typeToString(newCurrentDerivation[newCurrentDerivation.length - 1]) - ) - ) - diagnostics.push(diagnostic) - } - return { - _tag: "InvalidDerivation", - type: errorType - }; - } + function getAssertionTypeAndExpression(node: JSDocTypeAssertion | AssertionExpression) { + let type: TypeNode; + let expression: Expression; + switch (node.kind) { + case SyntaxKind.AsExpression: + case SyntaxKind.TypeAssertionExpression: + type = node.type; + expression = node.expression; + break; + case SyntaxKind.ParenthesizedExpression: + type = getJSDocTypeAssertionType(node); + expression = node.expression; + break; } - let hasRules = false; - if ((type.flags & TypeFlags.Object) && ((type as ObjectType).objectFlags & ObjectFlags.Reference) && !isTupleType(type) && type.symbol && type.symbol.declarations) { - const targetTypes = getTypeArguments(type as TypeReference); - const mergedRules = getTsPlusDerivationRules(type.symbol, targetTypes); - ruleCheck: for (const [rule, _, ruleDeclaration, options] of mergedRules.rules) { - const toCheck: Type[] = []; - if (rule.paramActions.length !== targetTypes.length) { - continue ruleCheck; - } - let index = 0; - for (const paramAction of rule.paramActions) { - if (paramAction === "_") { - toCheck.push(targetTypes[index]) - } else if (paramAction === "[]") { - if (isTupleType(targetTypes[index])) { - toCheck.push(targetTypes[index]) - } else { - continue ruleCheck; - } - } else if (paramAction === "|") { - if (targetTypes[index].flags & TypeFlags.Union) { - const typesInUnion = (targetTypes[index] as UnionType).types; - const typesInUnionTuple = createTupleType(typesInUnion, void 0, false, void 0); - toCheck.push(typesInUnionTuple) - } else { - continue ruleCheck; - } - } else if (paramAction === "&") { - if (targetTypes[index].flags & TypeFlags.Intersection) { - const typesInUnion = (targetTypes[index] as IntersectionType).types; - const typesInUnionTuple = createTupleType(typesInUnion, void 0, false, void 0); - toCheck.push(typesInUnionTuple) - } else { - continue ruleCheck; - } - } else { - continue ruleCheck; - } - index++; - } - if (toCheck.length !== targetTypes.length) { - continue ruleCheck; - } - const signatures = getSignaturesOfType(getTypeOfNode(ruleDeclaration), SignatureKind.Call); - for (const signature of signatures) { - if (signature.typeParameters && signature.typeParameters.length === toCheck.length) { - const mapper = createTypeMapper(signature.typeParameters, toCheck); - if (every(signature.typeParameters, (param, i) => { - const constraint = instantiateType(getConstraintOfTypeParameter(param), mapper); - if (!constraint) { - return true; - } - return isTypeAssignableTo(toCheck[i], constraint); - })) { - const instantiated = getSignatureInstantiation(signature, toCheck, false); - if (instantiated.parameters.length === 1 && - instantiated.parameters[0].valueDeclaration && - (instantiated.parameters[0].valueDeclaration as ParameterDeclaration).dotDotDotToken - ) { - const residualType = getTypeOfSymbol(instantiated.parameters[0]); - if (isTupleType(residualType)) { - const types = getTypeArguments(residualType); - const selfRule: FromRule = { - _tag: "FromRule", - type, - rule: ruleDeclaration, - arguments: [], - usedBy: [], - lazyRule: mergedRules.lazyRule - }; - const supportsLazy = !!mergedRules.lazyRule && !options.has("no-recursion"); - const newProhibited = !supportsLazy ? [...prohibited, type] : prohibited; - const newDerivationScope: FromRule[] = supportsLazy ? [...derivationScope, selfRule] : derivationScope; - const derivations = map(types, (childType) => deriveTypeWorker( - location, - originalType, - childType, - diagnostics, - newDerivationScope, - newProhibited, - newCurrentDerivation - )); - if (!find(derivations, (d) => isErrorType(d.type))) { - selfRule.arguments.push(...derivations); - return selfRule; - } - } - } - } - } - } - if (diagnostics.length > 0) { - break ruleCheck; + + return { type, expression }; + } + + function checkAssertionDeferred(node: JSDocTypeAssertion | AssertionExpression) { + const { type, expression } = getAssertionTypeAndExpression(node); + const errNode = isParenthesizedExpression(node) ? type : node; + const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(checkExpression(expression))); + const targetType = getTypeFromTypeNode(type); + if (!isErrorType(targetType)) { + addLazyDiagnostic(() => { + 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); } - } + }); } - if (diagnostics.length === 0 && !hasRules && isTupleType(type)) { - const props = getTypeArguments(type); - const derivations = map(props, (prop) => deriveTypeWorker( - location, - originalType, - prop, - diagnostics, - derivationScope, - prohibited, - newCurrentDerivation - )); - if (!find(derivations, (d) => isErrorType(d.type))) { - return { - _tag: "FromTupleStructure", - type, - fields: derivations - }; + } + + 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 checkExpressionWithTypeArguments(node: ExpressionWithTypeArguments | TypeQueryNode) { + checkGrammarExpressionWithTypeArguments(node); + forEach(node.typeArguments, checkSourceElement); + if (node.kind === SyntaxKind.ExpressionWithTypeArguments) { + const parent = walkUpParenthesizedExpressions(node.parent); + if (parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.InstanceOfKeyword && isNodeDescendantOf(node, (parent as BinaryExpression).right)) { + error(node, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_not_be_an_instantiation_expression); } } - if (diagnostics.length === 0 && !hasRules && (type.flags & TypeFlags.Intersection)) { - const props = (type as IntersectionType).types; - const canIntersect = !find(props, (p) => { - if ((p.flags & TypeFlags.Object) && getSignaturesOfType(p, SignatureKind.Call).length === 0 && getSignaturesOfType(p, SignatureKind.Construct).length === 0 && !isArrayOrTupleLikeType(p)) { - return false - } - return true - }) - if (canIntersect) { - const derivations = map(props, (prop) => deriveTypeWorker( - location, - originalType, - prop, - diagnostics, - derivationScope, - prohibited, - newCurrentDerivation - )); - if (!find(derivations, (d) => isErrorType(d.type))) { - return { - _tag: "FromIntersectionStructure", - type, - fields: derivations - }; - } - } + const exprType = node.kind === SyntaxKind.ExpressionWithTypeArguments ? checkExpression(node.expression) : + isThisIdentifier(node.exprName) ? checkThisExpression(node.exprName) : + checkExpression(node.exprName); + return getInstantiationExpressionType(exprType, node); + } + + function getInstantiationExpressionType(exprType: Type, node: NodeWithTypeArguments) { + const typeArguments = node.typeArguments; + if (exprType === silentNeverType || isErrorType(exprType) || !some(typeArguments)) { + return exprType; } - if (diagnostics.length === 0 && !hasRules && (type.flags & TypeFlags.Object)) { - const call = getSignaturesOfType(type, SignatureKind.Call); - const construct = getSignaturesOfType(type, SignatureKind.Construct); - if (call.length === 0 && construct.length === 0) { - const props = getPropertiesOfType(type); - if (!find(props, (p) => p.escapedName.toString().startsWith("__@"))) { - const fields: FromObjectStructure["fields"] = []; - for (const prop of props) { - fields.push({ - prop, - value: deriveTypeWorker( - location, - originalType, - getTypeOfSymbol(prop), - diagnostics, - derivationScope, - prohibited, - newCurrentDerivation - ) - }) + let hasSomeApplicableSignature = false; + let nonApplicableType: Type | undefined; + const result = getInstantiatedType(exprType); + const errorType = hasSomeApplicableSignature ? nonApplicableType : exprType; + if (errorType) { + diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Type_0_has_no_signatures_for_which_the_type_argument_list_is_applicable, typeToString(errorType))); + } + return result; + + function getInstantiatedType(type: Type): Type { + let hasSignatures = false; + let hasApplicableSignature = false; + const result = getInstantiatedTypePart(type); + hasSomeApplicableSignature ||= hasApplicableSignature; + if (hasSignatures && !hasApplicableSignature) { + nonApplicableType ??= type; + } + return result; + + function getInstantiatedTypePart(type: Type): Type { + if (type.flags & TypeFlags.Object) { + const resolved = resolveStructuredTypeMembers(type as ObjectType); + const callSignatures = getInstantiatedSignatures(resolved.callSignatures); + const constructSignatures = getInstantiatedSignatures(resolved.constructSignatures); + hasSignatures ||= resolved.callSignatures.length !== 0 || resolved.constructSignatures.length !== 0; + hasApplicableSignature ||= callSignatures.length !== 0 || constructSignatures.length !== 0; + if (callSignatures !== resolved.callSignatures || constructSignatures !== resolved.constructSignatures) { + const result = createAnonymousType(/*symbol*/ undefined, resolved.members, callSignatures, constructSignatures, resolved.indexInfos) as ResolvedType & InstantiationExpressionType; + result.objectFlags |= ObjectFlags.InstantiationExpressionType; + result.node = node; + return result; } - if (!find(fields, (d) => isErrorType(d.value.type))) { - return { - _tag: "FromObjectStructure", - type, - fields - }; + } + else if (type.flags & TypeFlags.InstantiableNonPrimitive) { + const constraint = getBaseConstraintOfType(type); + if (constraint) { + const instantiated = getInstantiatedTypePart(constraint); + if (instantiated !== constraint) { + return instantiated; + } } } + else if (type.flags & TypeFlags.Union) { + return mapType(type, getInstantiatedType); + } + else if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(sameMap((type as IntersectionType).types, getInstantiatedTypePart)); + } + return type; } } - if (diagnostics.length === 0 && !hasRules && (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral))) { - return { - _tag: "FromLiteral", - type, - value: (type as StringLiteralType | NumberLiteralType).value - }; + + function getInstantiatedSignatures(signatures: readonly Signature[]) { + const applicableSignatures = filter(signatures, sig => !!sig.typeParameters && hasCorrectTypeArgumentArity(sig, typeArguments)); + return sameMap(applicableSignatures, sig => { + const typeArgumentTypes = checkTypeArguments(sig, typeArguments!, /*reportErrors*/ true); + return typeArgumentTypes ? getSignatureInstantiation(sig, typeArgumentTypes, isInJSFile(sig.declaration)) : sig; + }); } - if (diagnostics.length === 0) { - if (isTypeIdenticalTo(originalType, type)) { - diagnostics.push(createError(location, Diagnostics.Cannot_derive_type_0_and_no_derivation_rules_are_found, typeToString(originalType))); - } else { - const digs: Diagnostic[] = []; - for (let i = 1; i < newCurrentDerivation.length - 1; i++) { - const step = newCurrentDerivation[i]; - digs.push(createError( - location, - Diagnostics.Failed_derivation_of_type_0, - typeToString(step) - )); - } - const diagnostic = createDiagnosticForNodeFromMessageChain( - sourceFile, - location, - chainDiagnosticMessages( - map(digs, createDiagnosticMessageChainFromDiagnostic), - Diagnostics.Cannot_derive_type_0_you_may_want_to_try_add_an_implicit_for_1_in_scope, - typeToString(newCurrentDerivation[0]), - typeToString(newCurrentDerivation[newCurrentDerivation.length - 1]) - ) - ) - diagnostics.push(diagnostic) - } + } + + function checkSatisfiesExpression(node: SatisfiesExpression) { + checkSourceElement(node.type); + return checkSatisfiesExpressionWorker(node.expression, node.type); + } + + function checkSatisfiesExpressionWorker(expression: Expression, target: TypeNode, checkMode?: CheckMode) { + const exprType = checkExpression(expression, checkMode); + const targetType = getTypeFromTypeNode(target); + if (isErrorType(targetType)) { + return targetType; } - return { - _tag: "InvalidDerivation", - type: errorType - }; + const errorNode = findAncestor(target.parent, n => n.kind === SyntaxKind.SatisfiesExpression || n.kind === SyntaxKind.JSDocSatisfiesTag); + checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, errorNode, expression, Diagnostics.Type_0_does_not_satisfy_the_expected_type_1); + return exprType; } - function tsPlusDoGetBindOutput(node: CallExpression, resultType: Type, statementNode: Statement) { - const mapFluent = getFluentExtension(resultType, "map"); - if (mapFluent) { - for (const signature of getSignaturesOfType(mapFluent, SignatureKind.Call)) { - const original = (signature as TsPlusSignature).tsPlusOriginal; - if (original.minArgumentCount === 2 && original.typeParameters) { - const context = createInferenceContext(original.typeParameters, original, InferenceFlags.None); - inferTypes(context.inferences, resultType, getTypeOfSymbol(original.parameters[0])); - const instantiated = getSignatureInstantiation(original, getInferredTypes(context), /* isJavascript */ false); - if (isTypeAssignableTo(resultType, getTypeOfSymbol(instantiated.parameters[0]))) { - const funcType = getTypeOfSymbol(instantiated.parameters[1]); - const statementLinks = getNodeLinks(statementNode); - statementLinks.tsPlusDoBindType = [node, resultType]; - return getTypeOfSymbol(getSignaturesOfType(funcType, SignatureKind.Call)[0].parameters[0]); - } - } - } + + function checkMetaProperty(node: MetaProperty): Type { + checkGrammarMetaProperty(node); + + if (node.keywordToken === SyntaxKind.NewKeyword) { + return checkNewTargetMetaProperty(node); } - diagnostics.add(error(node, Diagnostics.Cannot_find_a_valid_flatMap_extension_for_type_0, typeToString(resultType))); - return errorType; + + if (node.keywordToken === SyntaxKind.ImportKeyword) { + return checkImportMetaProperty(node); + } + + return Debug.assertNever(node.keywordToken); } - function tsPlusDoCheckBind(node: CallExpression, resultType: Type) { - const links = getNodeLinks(node); - if (!links.tsPlusResolvedType) { - links.tsPlusResolvedType = tsPlusDoCheckBindWorker(node, resultType); + + function checkMetaPropertyKeyword(node: MetaProperty): Type { + switch (node.keywordToken) { + case SyntaxKind.ImportKeyword: + return getGlobalImportMetaExpressionType(); + case SyntaxKind.NewKeyword: + const type = checkNewTargetMetaProperty(node); + return isErrorType(type) ? errorType : createNewTargetExpressionType(type); + default: + Debug.assertNever(node.keywordToken); } - return links.tsPlusResolvedType; } - function tsPlusDoCheckBindWorker(node: CallExpression, resultType: Type) { - if ( - isVariableDeclaration(node.parent) && - isVariableDeclarationList(node.parent.parent) && - (node.parent.parent.flags & NodeFlags.Const) && - isVariableStatement(node.parent.parent.parent) && - isBlock(node.parent.parent.parent.parent) && - isArrowFunction(node.parent.parent.parent.parent.parent) && - isCallExpression(node.parent.parent.parent.parent.parent.parent) - ) { - const callExp = node.parent.parent.parent.parent.parent.parent as CallExpression; - const links = getNodeLinks(callExp); - if (links.isTsPlusDoCall) { - return tsPlusDoGetBindOutput(node, resultType, node.parent.parent.parent); - } + + 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 ( - isExpressionStatement(node.parent) && - isBlock(node.parent.parent) && - isArrowFunction(node.parent.parent.parent) && - isCallExpression(node.parent.parent.parent.parent) - ) { - const callExp = node.parent.parent.parent.parent as CallExpression; - const links = getNodeLinks(callExp); - if (links.isTsPlusDoCall) { - return tsPlusDoGetBindOutput(node, resultType, node.parent); - } + else if (container.kind === SyntaxKind.Constructor) { + const symbol = getSymbolOfDeclaration(container.parent); + return getTypeOfSymbol(symbol); } - else if ( - isReturnStatement(node.parent) && - isBlock(node.parent.parent) && - isArrowFunction(node.parent.parent.parent) && - isCallExpression(node.parent.parent.parent.parent) - ) { - const callExp = node.parent.parent.parent.parent as CallExpression; - const links = getNodeLinks(callExp); - if (links.isTsPlusDoCall) { - links.isTsPlusDoReturnBound = true; - return tsPlusDoGetBindOutput(node, resultType, node.parent); - } + else { + const symbol = getSymbolOfDeclaration(container)!; + return getTypeOfSymbol(symbol); } - diagnostics.add(error(node, Diagnostics.A_call_to_bind_is_only_allowed_in_the_context_of_a_Do)); - return errorType; } - function tsPlusDoCheckChain(types: readonly [CallExpression, Type][], signature: Signature): Type { - let current = types[types.length - 1][1]; - for (let i = types.length - 2; i >= 0; i--) { - const [node, type] = types[i]; - const context = createInferenceContext(signature.typeParameters!, signature, InferenceFlags.None); - inferTypes(context.inferences, type, getTypeOfSymbol(signature.parameters[0])); - const childType = createAnonymousType( - void 0, - emptySymbols, - [createSignature( - void 0, - [], - void 0, - [], - current, - void 0, - 0, - SignatureFlags.None - )], - [], - [] - ); - inferTypes(context.inferences, childType, getTypeOfSymbol(signature.parameters[1])); - const instantiated = getSignatureInstantiation(signature, getInferredTypes(context), /* isJavascript */ false); - if (!isTypeAssignableTo(type, getTypeOfSymbol(instantiated.parameters[0]))) { - error(node, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(type), typeToString(getTypeOfSymbol(instantiated.parameters[0]))); - return errorType; - } - if (!isTypeAssignableTo(childType, getTypeOfSymbol(instantiated.parameters[1]))) { - error(node, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(childType), typeToString(getTypeOfSymbol(instantiated.parameters[1]))); - return errorType; + + function checkImportMetaProperty(node: MetaProperty) { + if (moduleKind === ModuleKind.Node16 || moduleKind === ModuleKind.NodeNext) { + if (getSourceFileOfNode(node).impliedNodeFormat !== ModuleKind.ESNext) { + error(node, Diagnostics.The_import_meta_meta_property_is_not_allowed_in_files_which_will_build_into_CommonJS_output); } - current = getReturnTypeOfSignature(instantiated); - if (isErrorType(current)) { - return errorType; + } + else if (moduleKind < ModuleKind.ES2020 && moduleKind !== ModuleKind.System) { + error(node, Diagnostics.The_import_meta_meta_property_is_only_allowed_when_the_module_option_is_es2020_es2022_esnext_system_node16_or_nodenext); + } + const file = getSourceFileOfNode(node); + Debug.assert(!!(file.flags & NodeFlags.PossiblyContainsImportMeta), "Containing file is missing import meta node flag."); + 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 current; + return type; } - function tsPlusDoCheckLastBind(node: CallExpression, mapFunction: Signature, lastBind: Type, result: Type) { - const context = createInferenceContext(mapFunction.typeParameters!, mapFunction, InferenceFlags.None); - const createdFunction = createAnonymousType( - void 0, - emptySymbols, - [createSignature( - void 0, - [], - void 0, - [], - result, - void 0, - 0, - SignatureFlags.None - )], - [], - [] - ); - inferTypes(context.inferences, lastBind, getTypeOfSymbol(mapFunction.parameters[0])); - inferTypes(context.inferences, createdFunction, getTypeOfSymbol(mapFunction.parameters[1])); - const instantiated = getSignatureInstantiation(mapFunction, getInferredTypes(context), /* isJavascript */ false); - if (!isTypeAssignableTo(lastBind, getTypeOfSymbol(instantiated.parameters[0]))) { - error(node, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(lastBind), typeToString(getTypeOfSymbol(instantiated.parameters[0]))); - return errorType; + + 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; } - if (!isTypeAssignableTo(createdFunction, getTypeOfSymbol(instantiated.parameters[1]))) { - error(node, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(createdFunction), typeToString(getTypeOfSymbol(instantiated.parameters[1]))); - return errorType; + const restParameter = signature.parameters[paramCount] || unknownSymbol; + const restType = overrideRestType || getTypeOfSymbol(restParameter); + if (isTupleType(restType)) { + const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; + const index = pos - paramCount; + return associatedNames && getTupleElementLabel(associatedNames[index]) || restParameter.escapedName + "_" + index as __String; } - return getReturnTypeOfSignature(instantiated); + return restParameter.escapedName; } - function tsPlusDoChooseImplementation(types: [CallExpression, Type][], name: string) { - const seen = new Set(); - for (const [_, type] of types) { - const flatMap = getFluentExtension(type, name); - if (flatMap) { - for (const signature of getSignaturesOfType(flatMap, SignatureKind.Call)) { - const original = (signature as TsPlusSignature).tsPlusOriginal; - if (!seen.has(original)) { - seen.add(original); - if (original.minArgumentCount === 2 && original.typeParameters) { - const mapper = createTypeMapper(original.typeParameters, original.typeParameters.map(() => anyType)); - const p0 = instantiateType(getTypeOfSymbol(original.parameters[0]), mapper); - if (types.every((target) => isTypeAssignableTo(target[1], p0))) { - return signature as TsPlusSignature; - } - } - } - } - } + + function getParameterIdentifierNameAtPosition(signature: Signature, pos: number): [parameterName: __String, isRestParameter: boolean] | undefined { + if (signature.declaration?.kind === SyntaxKind.JSDocFunctionType) { + return undefined; + } + const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); + if (pos < paramCount) { + const param = signature.parameters[pos]; + return isParameterDeclarationWithIdentifierName(param) ? [param.escapedName, false] : undefined; + } + + const restParameter = signature.parameters[paramCount] || unknownSymbol; + if (!isParameterDeclarationWithIdentifierName(restParameter)) { + return undefined; + } + + const restType = getTypeOfSymbol(restParameter); + if (isTupleType(restType)) { + const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; + const index = pos - paramCount; + const associatedName = associatedNames?.[index]; + const isRestTupleElement = !!associatedName?.dotDotDotToken; + return associatedName ? [ + getTupleElementLabel(associatedName), + isRestTupleElement + ] : undefined; + } + + if (pos === paramCount) { + return [restParameter.escapedName, true]; } + return undefined; } - function tsPlusDoCheckDo(node: CallExpression, checked: Type) { - const links = getNodeLinks(node); - if (!links.tsPlusResolvedType) { - links.tsPlusResolvedType = tsPlusDoCheckDoWorker(node, checked); + + function isParameterDeclarationWithIdentifierName(symbol: Symbol) { + return symbol.valueDeclaration && isParameter(symbol.valueDeclaration) && isIdentifier(symbol.valueDeclaration.name); + } + 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; } - return links.tsPlusResolvedType; + const restParameter = signature.parameters[paramCount] || unknownSymbol; + const restType = getTypeOfSymbol(restParameter); + if (isTupleType(restType)) { + const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; + const index = pos - paramCount; + return associatedNames && associatedNames[index]; + } + return restParameter.valueDeclaration && isValidDeclarationForTupleLabel(restParameter.valueDeclaration) ? restParameter.valueDeclaration : undefined; } - function tsPlusDoCheckDoWorker(node: CallExpression, checked: Type) { - if (isArrowFunction(node.arguments[0]) && isBlock(node.arguments[0].body)) { - const types: [CallExpression, Type][] = []; - forEach(node.arguments[0].body.statements, (statement) => { - checkSourceElement(statement); - const links = getNodeLinks(statement); - if (links.tsPlusDoBindType) { - types.push(links.tsPlusDoBindType); - } - }); - if (types.length === 0) { - error(node, Diagnostics.A_Do_block_must_contain_at_least_1_bound_value); - return errorType; - } - getNodeLinks(node).tsPlusDoBindTypes = types; - const mapFn = tsPlusDoChooseImplementation(types, "map"); - if (!mapFn) { - error(node, Diagnostics.Cannot_find_a_valid_0_that_can_handle_all_the_types_of_1, "map", typeToString(getUnionType(types.map((t) => t[1])))) - return errorType; - } - const flatMapFn = tsPlusDoChooseImplementation(types, "flatMap"); - if (!flatMapFn) { - error(node, Diagnostics.Cannot_find_a_valid_0_that_can_handle_all_the_types_of_1, "flatMap", typeToString(getUnionType(types.map((t) => t[1])))) - return errorType; + + 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, getNumberLiteralType(index)); } - getNodeLinks(node).tsPlusDoFunctions = { - flatMap: flatMapFn, - map: mapFn - }; - const lastBind = types[types.length - 1]; - const links = getNodeLinks(node); - const mapped = links.isTsPlusDoReturnBound ? lastBind[1] : tsPlusDoCheckLastBind(lastBind[0], mapFn.tsPlusOriginal, lastBind[1], checked) - const toBeFlatMapped = types.map((t, i) => i !== types.length - 1 ? t : [t[0], mapped] as typeof t); - return tsPlusDoCheckChain(toBeFlatMapped, flatMapFn.tsPlusOriginal); } - error(node, Diagnostics.The_first_argument_of_a_Do_must_be_an_arrow_function); - return errorType; + return undefined; } - function checkCallExpression(node: CallExpression | NewExpression, checkMode?: CheckMode): Type { - let checked = checkCallExpressionOriginal(node, checkMode); - if (isTsPlusMacroCall(node, "Bind") && !isErrorType(checked)) { - return tsPlusDoCheckBind(node, checked); - } - if (isTsPlusMacroCall(node, "Do") && !isErrorType(checked)) { - return tsPlusDoCheckDo(node, checked); + + 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)); } - if (isTsPlusMacroCall(node, "Derive") && !isErrorType(checked)) { - return deriveType(node, checked); + 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); + } } - if (isTsPlusMacroCall(node, "pipeable") && !isErrorType(checked)) { - if (!isVariableDeclaration(node.parent)) { - error(node, Diagnostics.Pipeable_macro_is_only_allowed_in_variable_declarations); - return checked; + return createTupleType(types, flags, /*readonly*/ false, length(names) === length(types) ? names : undefined); + } + + // Return the number of parameters in a signature. The rest parameter, if present, counts as one + // parameter. For example, the parameter count of (x: number, y: number, ...z: string[]) is 3 and + // the parameter count of (x: number, ...args: [number, ...string[], boolean])) is also 3. In the + // latter example, the effective rest type is [...string[], boolean]. + 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); } - if (node.arguments.length > 0) { - const dataFirstSymbol = getTypeOfNode(node.arguments[0]).symbol; - if (dataFirstSymbol && dataFirstSymbol.declarations) { - const dataFirstDeclaration = find(dataFirstSymbol.declarations, (decl): decl is FunctionDeclaration | ArrowFunction | FunctionExpression => isFunctionDeclaration(decl) || isArrowFunction(decl) || isFunctionExpression(decl)) - if (dataFirstDeclaration) { - const type = generatePipeable(node.parent, dataFirstDeclaration); - if (type) { - getNodeLinks(node.parent).tsPlusDataFirstDeclaration = dataFirstDeclaration; - return type; - } + } + 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 (isTsPlusMacroCall(node, "identity") && !isErrorType(checked)) { - if (node.arguments.length === 1) { - if (isSpreadArgument(node.arguments[0])) { - error(node.arguments[0], Diagnostics.The_argument_of_an_identity_macro_must_not_be_a_spread_element); - return checked; + if (minArgumentCount === undefined) { + if (!strongArityForUntypedJS && signature.flags & SignatureFlags.IsUntypedSignatureInJSFile) { + return 0; + } + minArgumentCount = signature.minArgumentCount; + } + for (let i = 0; i < signature.parameters.length; i++) { + if (signature.parameters[i].valueDeclaration && (signature.parameters[i].valueDeclaration as ParameterDeclaration).isAuto) { + minArgumentCount = signature.parameters.length - (signature.parameters.length - i); + break; } } + 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; } - if (isCallExpression(node)) { - tryCacheOptimizedPipeableCall(node); - } - return checked; + return signature.resolvedMinArgumentCount; } - // TSPLUS EXTENSION END - - /** - * 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 checkCallExpressionOriginal(node: CallExpression | NewExpression, checkMode?: CheckMode): Type { - checkGrammarTypeArguments(node, node.typeArguments); - 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 silentNeverType. - return silentNeverType; + function hasEffectiveRestParameter(signature: Signature) { + if (signatureHasRestParameter(signature)) { + const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); + return !isTupleType(restType) || restType.target.hasRestElement; } + return false; + } - checkDeprecatedSignature(signature, node); - - if (node.expression.kind === SyntaxKind.SuperKeyword) { - return voidType; + 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; + } - if (node.kind === SyntaxKind.NewExpression) { - const declaration = signature.declaration; + function getNonArrayRestType(signature: Signature) { + const restType = getEffectiveRestType(signature); + return restType && !isArrayType(restType) && !isTypeAny(restType) ? restType : undefined; + } - if (declaration && - declaration.kind !== SyntaxKind.Constructor && - declaration.kind !== SyntaxKind.ConstructSignature && - declaration.kind !== SyntaxKind.ConstructorType && - !(isJSDocSignature(declaration) && getJSDocRoot(declaration)?.parent?.kind === SyntaxKind.Constructor) && - !isJSDocConstructSignature(declaration) && - !isJSConstructor(declaration)) { + function getTypeOfFirstParameterOfSignature(signature: Signature) { + return getTypeOfFirstParameterOfSignatureWithFallback(signature, neverType); + } - // 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); + 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 as ParameterDeclaration; + if (declaration.type) { + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + inferTypes(inferenceContext.inferences, getTypeFromTypeNode(typeNode), getTypeAtPosition(context, i)); } - return anyType; } } + } - // In JavaScript files, calls to any identifier 'require' are treated as external module imports - if (isInJSFile(node) && shouldResolveJsRequire(compilerOptions) && 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)); + function assignContextualParameterTypes(signature: Signature, context: Signature) { + if (context.typeParameters) { + if (!signature.typeParameters) { + signature.typeParameters = context.typeParameters; + } + else { + return; // This signature has already has a contextual inference performed and cached on it! + } } - if (node.kind === SyntaxKind.CallExpression && !node.questionDotToken && 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); + if (context.thisParameter) { + const parameter = signature.thisParameter; + if (!parameter || parameter.valueDeclaration && !(parameter.valueDeclaration as ParameterDeclaration).type) { + if (!parameter) { + signature.thisParameter = createSymbolWithType(context.thisParameter, /*type*/ undefined); + } + assignParameterType(signature.thisParameter!, getTypeOfSymbol(context.thisParameter)); } - 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); + } + 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 as ParameterDeclaration)) { + const contextualParameterType = tryGetTypeAtPosition(context, i); + assignParameterType(parameter, contextualParameterType); } } - - if (isInJSFile(node)) { - const jsSymbol = getSymbolOfExpando(node, /*allowDeclaration*/ false); - if (jsSymbol?.exports?.size) { - const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, emptyArray); - jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral; - return getIntersectionType([returnType, jsAssignmentType]); + if (signatureHasRestParameter(signature)) { + // parameter might be a transient symbol generated by use of `arguments` in the function body. + const parameter = last(signature.parameters); + if (parameter.valueDeclaration + ? !getEffectiveTypeAnnotationNode(parameter.valueDeclaration as ParameterDeclaration) + // a declarationless parameter may still have a `.type` already set by its construction logic + // (which may pull a type from a jsdoc) - only allow fixing on `DeferredType` parameters with a fallback type + : !!(getCheckFlags(parameter) & CheckFlags.DeferredType) + ) { + const contextualParameterType = getRestTypeAtPosition(context, len); + assignParameterType(parameter, contextualParameterType); } } - - return returnType; } - function checkDeprecatedSignature(signature: Signature, node: CallLikeExpression) { - if (signature.declaration && signature.declaration.flags & NodeFlags.Deprecated) { - const suggestionNode = getDeprecatedSuggestionNode(node); - const name = tryGetPropertyAccessOrIdentifierToString(getInvokedExpression(node)); - addDeprecatedSuggestionWithSignature(suggestionNode, signature.declaration, name, signatureToString(signature)); + function assignNonContextualParameterTypes(signature: Signature) { + if (signature.thisParameter) { + assignParameterType(signature.thisParameter); } - } - - function getDeprecatedSuggestionNode(node: Node): Node { - node = skipParentheses(node); - switch (node.kind) { - case SyntaxKind.CallExpression: - case SyntaxKind.Decorator: - case SyntaxKind.NewExpression: - return getDeprecatedSuggestionNode((node as Decorator | CallExpression | NewExpression).expression); - case SyntaxKind.TaggedTemplateExpression: - return getDeprecatedSuggestionNode((node as TaggedTemplateExpression).tag); - case SyntaxKind.JsxOpeningElement: - case SyntaxKind.JsxSelfClosingElement: - return getDeprecatedSuggestionNode((node as JsxOpeningLikeElement).tagName); - case SyntaxKind.ElementAccessExpression: - return (node as ElementAccessExpression).argumentExpression; - case SyntaxKind.PropertyAccessExpression: - return (node as PropertyAccessExpression).name; - case SyntaxKind.TypeReference: - const typeReference = node as TypeReferenceNode; - return isQualifiedName(typeReference.typeName) ? typeReference.typeName.right : typeReference; - default: - return node; + for (const parameter of signature.parameters) { + assignParameterType(parameter); } } - function isSymbolOrSymbolForCall(node: Node) { - if (!isCallExpression(node)) return false; - let left = node.expression; - if (isPropertyAccessExpression(left) && left.name.escapedText === "for") { - left = left.expression; + function assignParameterType(parameter: Symbol, type?: Type) { + const links = getSymbolLinks(parameter); + if (!links.type) { + const declaration = parameter.valueDeclaration as ParameterDeclaration | undefined; + links.type = type || (declaration ? getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true) : getTypeOfSymbol(parameter)); + if (declaration && 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, links.type); + } } - if (!isIdentifier(left) || left.escapedText !== "Symbol") { - return false; + else if (type) { + Debug.assertEqual(links.type, type, "Parameter symbol already has a cached type which differs from newly assigned type"); } + } - // make sure `Symbol` is the global symbol - const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); - if (!globalESSymbol) { - return false; + // 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, parentType: Type) { + for (const element of pattern.elements) { + if (!isOmittedExpression(element)) { + const type = getBindingElementTypeFromParentType(element, parentType); + if (element.name.kind === SyntaxKind.Identifier) { + getSymbolLinks(getSymbolOfDeclaration(element)).type = type; + } + else { + assignBindingElementTypes(element.name, type); + } + } } + } - return globalESSymbol === resolveName(left, "Symbol" as __String, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false); + function createClassDecoratorContextType(classType: Type) { + return tryCreateTypeReference(getGlobalClassDecoratorContextType(/*reportErrors*/ true), [classType]); } - function checkImportCallExpression(node: ImportCall): Type { - // Check grammar of dynamic import - checkGrammarImportCallExpression(node); + function createClassMethodDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassMethodDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } - if (node.arguments.length === 0) { - return createPromiseReturnType(node, anyType); - } + function createClassGetterDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassGetterDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } - const specifier = node.arguments[0]; - const specifierType = checkExpressionCached(specifier); - const optionsType = node.arguments.length > 1 ? checkExpressionCached(node.arguments[1]) : undefined; - // Even though multiple arguments is grammatically incorrect, type-check extra arguments for completion - for (let i = 2; i < node.arguments.length; ++i) { - checkExpressionCached(node.arguments[i]); - } + function createClassSetterDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassSetterDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } - 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)); - } + function createClassAccessorDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassAccessorDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } - if (optionsType) { - const importCallOptionsType = getGlobalImportCallOptionsType(/*reportErrors*/ true); - if (importCallOptionsType !== emptyObjectType) { - checkTypeAssignableTo(optionsType, getNullableType(importCallOptionsType, TypeFlags.Undefined), node.arguments[1]); - } - } + function createClassFieldDecoratorContextType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassFieldDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); + } - // resolveExternalModuleName will return undefined if the moduleReferenceExpression is not a string literal - const moduleSymbol = resolveExternalModuleName(node, specifier); - if (moduleSymbol) { - const esModuleSymbol = resolveESModuleSymbol(moduleSymbol, specifier, /*dontResolveAlias*/ true, /*suppressInteropError*/ false); - if (esModuleSymbol) { - return createPromiseReturnType(node, - getTypeWithSyntheticDefaultOnly(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) || - getTypeWithSyntheticDefaultImportType(getTypeOfSymbol(esModuleSymbol), esModuleSymbol, moduleSymbol, specifier) - ); - } + /** + * Gets a type like `{ name: "foo", private: false, static: true }` that is used to provided member-specific + * details that will be intersected with a decorator context type. + */ + function getClassMemberDecoratorContextOverrideType(nameType: Type, isPrivate: boolean, isStatic: boolean) { + const key = `${isPrivate ? "p" : "P"}${isStatic ? "s" : "S"}${nameType.id}` as const; + let overrideType = decoratorContextOverrideTypeCache.get(key); + if (!overrideType) { + const members = createSymbolTable(); + members.set("name" as __String, createProperty("name" as __String, nameType)); + members.set("private" as __String, createProperty("private" as __String, isPrivate ? trueType : falseType)); + members.set("static" as __String, createProperty("static" as __String, isStatic ? trueType : falseType)); + overrideType = createAnonymousType(/*symbol*/ undefined, members, emptyArray, emptyArray, emptyArray); + decoratorContextOverrideTypeCache.set(key, overrideType); } - return createPromiseReturnType(node, anyType); + return overrideType; } - function createDefaultPropertyWrapperForModule(symbol: Symbol, originalSymbol: Symbol | undefined, anonymousSymbol?: Symbol | undefined) { - const memberTable = createSymbolTable(); - const newSymbol = createSymbol(SymbolFlags.Alias, InternalSymbolName.Default); - newSymbol.parent = originalSymbol; - newSymbol.links.nameType = getStringLiteralType("default"); - newSymbol.links.aliasTarget = resolveSymbol(symbol); - memberTable.set(InternalSymbolName.Default, newSymbol); - return createAnonymousType(anonymousSymbol, memberTable, emptyArray, emptyArray, emptyArray); + function createClassMemberDecoratorContextTypeForNode(node: MethodDeclaration | AccessorDeclaration | PropertyDeclaration, thisType: Type, valueType: Type) { + const isStatic = hasStaticModifier(node); + const isPrivate = isPrivateIdentifier(node.name); + const nameType = isPrivate ? getStringLiteralType(idText(node.name)) : getLiteralTypeFromPropertyName(node.name); + const contextType = + isMethodDeclaration(node) ? createClassMethodDecoratorContextType(thisType, valueType) : + isGetAccessorDeclaration(node) ? createClassGetterDecoratorContextType(thisType, valueType) : + isSetAccessorDeclaration(node) ? createClassSetterDecoratorContextType(thisType, valueType) : + isAutoAccessorPropertyDeclaration(node) ? createClassAccessorDecoratorContextType(thisType, valueType) : + isPropertyDeclaration(node) ? createClassFieldDecoratorContextType(thisType, valueType) : + Debug.failBadSyntaxKind(node); + const overrideType = getClassMemberDecoratorContextOverrideType(nameType, isPrivate, isStatic); + return getIntersectionType([contextType, overrideType]); + + } + + function createClassAccessorDecoratorTargetType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassAccessorDecoratorTargetType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassAccessorDecoratorResultType(thisType: Type, valueType: Type) { + return tryCreateTypeReference(getGlobalClassAccessorDecoratorResultType(/*reportErrors*/ true), [thisType, valueType]); + } + + function createClassFieldDecoratorInitializerMutatorType(thisType: Type, valueType: Type) { + const thisParam = createParameter("this" as __String, thisType); + const valueParam = createParameter("value" as __String, valueType); + return createFunctionType(/*typeParameters*/ undefined, thisParam, [valueParam], valueType, /*typePredicate*/ undefined, 1); + } + + /** + * Creates a call signature for an ES Decorator. This method is used by the semantics of + * `getESDecoratorCallSignature`, which you should probably be using instead. + */ + function createESDecoratorCallSignature(targetType: Type, contextType: Type, nonOptionalReturnType: Type) { + const targetParam = createParameter("target" as __String, targetType); + const contextParam = createParameter("context" as __String, contextType); + const returnType = getUnionType([nonOptionalReturnType, voidType]); + return createCallSignature(/*typeParameters*/ undefined, /*thisParameter*/ undefined, [targetParam, contextParam], returnType); } - function getTypeWithSyntheticDefaultOnly(type: Type, symbol: Symbol, originalSymbol: Symbol, moduleSpecifier: Expression) { - const hasDefaultOnly = isOnlyImportedAsDefault(moduleSpecifier); - if (hasDefaultOnly && type && !isErrorType(type)) { - const synthType = type as SyntheticDefaultModuleType; - if (!synthType.defaultOnlyType) { - const type = createDefaultPropertyWrapperForModule(symbol, originalSymbol); - synthType.defaultOnlyType = type; - } - return synthType.defaultOnlyType; - } - return undefined; - } + /** + * Gets a call signature that should be used when resolving `decorator` as a call. This does not use the value + * of the decorator itself, but instead uses the declaration on which it is placed along with its relative + * position amongst other decorators on the same declaration to determine the applicable signature. The + * resulting signature can be used for call resolution, inference, and contextual typing. + */ + function getESDecoratorCallSignature(decorator: Decorator) { + // We are considering a future change that would allow the type of a decorator to affect the type of the + // class and its members, such as a `@Stringify` decorator changing the type of a `number` field to `string`, or + // a `@Callable` decorator adding a call signature to a `class`. The type arguments for the various context + // types may eventually change to reflect such mutations. + // + // In some cases we describe such potential mutations as coming from a "prior decorator application". It is + // important to note that, while decorators are *evaluated* left to right, they are *applied* right to left + // to preserve f ৹ g -> f(g(x)) application order. In these cases, a "prior" decorator usually means the + // next decorator following this one in document order. + // + // The "original type" of a class or member is the type it was declared as, or the type we infer from + // initializers, before _any_ decorators are applied. + // + // The type of a class or member that is a result of a prior decorator application represents the + // "current type", i.e., the type for the declaration at the time the decorator is _applied_. + // + // The type of a class or member that is the result of the application of *all* relevant decorators is the + // "final type". + // + // Any decorator that allows mutation or replacement will also refer to an "input type" and an + // "output type". The "input type" corresponds to the "current type" of the declaration, while the + // "output type" will become either the "input type/current type" for a subsequent decorator application, + // or the "final type" for the decorated declaration. + // + // It is important to understand decorator application order as it relates to how the "current", "input", + // "output", and "final" types will be determined: + // + // @E2 @E1 class SomeClass { + // @A2 @A1 static f() {} + // @B2 @B1 g() {} + // @C2 @C1 static x; + // @D2 @D1 y; + // } + // + // Per [the specification][1], decorators are applied in the following order: + // + // 1. For each static method (incl. get/set methods and `accessor` fields), in document order: + // a. Apply each decorator for that method, in reverse order (`A1`, `A2`). + // 2. For each instance method (incl. get/set methods and `accessor` fields), in document order: + // a. Apply each decorator for that method, in reverse order (`B1`, `B2`). + // 3. For each static field (excl. auto-accessors), in document order: + // a. Apply each decorator for that field, in reverse order (`C1`, `C2`). + // 4. For each instance field (excl. auto-accessors), in document order: + // a. Apply each decorator for that field, in reverse order (`D1`, `D2`). + // 5. Apply each decorator for the class, in reverse order (`E1`, `E2`). + // + // As a result, "current" types at each decorator application are as follows: + // - For `A1`, the "current" types of the class and method are their "original" types. + // - For `A2`, the "current type" of the method is the "output type" of `A1`, and the "current type" of the + // class is the type of `SomeClass` where `f` is the "output type" of `A1`. This becomes the "final type" + // of `f`. + // - For `B1`, the "current type" of the method is its "original type", and the "current type" of the class + // is the type of `SomeClass` where `f` now has its "final type". + // - etc. + // + // [1]: https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-runtime-semantics-classdefinitionevaluation + // + // This seems complicated at first glance, but is not unlike our existing inference for functions: + // + // declare function pipe( + // original: Original, + // a1: (input: Original, context: Context) => A1, + // a2: (input: A1, context: Context) => A2, + // b1: (input: A2, context: Context) => B1, + // b2: (input: B1, context: Context) => B2, + // c1: (input: B2, context: Context) => C1, + // c2: (input: C1, context: Context) => C2, + // d1: (input: C2, context: Context) => D1, + // d2: (input: D1, context: Context) => D2, + // e1: (input: D2, context: Context) => E1, + // e2: (input: E1, context: Context) => E2, + // ): E2; + + // When a decorator is applied, it is passed two arguments: "target", which is a value representing the + // thing being decorated (constructors for classes, functions for methods/accessors, `undefined` for fields, + // and a `{ get, set }` object for auto-accessors), and "context", which is an object that provides + // reflection information about the decorated element, as well as the ability to add additional "extra" + // initializers. In most cases, the "target" argument corresponds to the "input type" in some way, and the + // return value similarly corresponds to the "output type" (though if the "output type" is `void` or + // `undefined` then the "output type" is the "input type"). + + const { parent } = decorator; + const links = getNodeLinks(parent); + if (!links.decoratorSignature) { + links.decoratorSignature = anySignature; + switch (parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: { + // Class decorators have a `context` of `ClassDecoratorContext`, where the `Class` type + // argument will be the "final type" of the class after all decorators are applied. + + const node = parent as ClassDeclaration | ClassExpression; + const targetType = getTypeOfSymbol(getSymbolOfDeclaration(node)); + const contextType = createClassDecoratorContextType(targetType); + links.decoratorSignature = createESDecoratorCallSignature(targetType, contextType, targetType); + break; + } + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: { + const node = parent as MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; + if (!isClassLike(node.parent)) break; + + // Method decorators have a `context` of `ClassMethodDecoratorContext`, where the + // `Value` type argument corresponds to the "final type" of the method. + // + // Getter decorators have a `context` of `ClassGetterDecoratorContext`, where the + // `Value` type argument corresponds to the "final type" of the value returned by the getter. + // + // Setter decorators have a `context` of `ClassSetterDecoratorContext`, where the + // `Value` type argument corresponds to the "final type" of the parameter of the setter. + // + // In all three cases, the `This` type argument is the "final type" of either the class or + // instance, depending on whether the member was `static`. + + const valueType = + isMethodDeclaration(node) ? getOrCreateTypeFromSignature(getSignatureFromDeclaration(node)) : + getTypeOfNode(node); + + const thisType = hasStaticModifier(node) ? + getTypeOfSymbol(getSymbolOfDeclaration(node.parent)) : + getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(node.parent)); + + // We wrap the "input type", if necessary, to match the decoration target. For getters this is + // something like `() => inputType`, for setters it's `(value: inputType) => void` and for + // methods it is just the input type. + const targetType = + isGetAccessorDeclaration(node) ? createGetterFunctionType(valueType) : + isSetAccessorDeclaration(node) ? createSetterFunctionType(valueType) : + valueType; + + const contextType = createClassMemberDecoratorContextTypeForNode(node, thisType, valueType); + + // We also wrap the "output type", as needed. + const returnType = + isGetAccessorDeclaration(node) ? createGetterFunctionType(valueType) : + isSetAccessorDeclaration(node) ? createSetterFunctionType(valueType) : + valueType; + + links.decoratorSignature = createESDecoratorCallSignature(targetType, contextType, returnType); + break; + } + + case SyntaxKind.PropertyDeclaration: { + const node = parent as PropertyDeclaration; + if (!isClassLike(node.parent)) break; + + // Field decorators have a `context` of `ClassFieldDecoratorContext` and + // auto-accessor decorators have a `context` of `ClassAccessorDecoratorContext. In + // both cases, the `This` type argument is the "final type" of either the class or instance, + // depending on whether the member was `static`, and the `Value` type argument corresponds to + // the "final type" of the value stored in the field. + + const valueType = getTypeOfNode(node); + const thisType = hasStaticModifier(node) ? + getTypeOfSymbol(getSymbolOfDeclaration(node.parent)) : + getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(node.parent)); + + // The `target` of an auto-accessor decorator is a `{ get, set }` object, representing the + // runtime-generated getter and setter that are added to the class/prototype. The `target` of a + // regular field decorator is always `undefined` as it isn't installed until it is initialized. + const targetType = + hasAccessorModifier(node) ? createClassAccessorDecoratorTargetType(thisType, valueType) : + undefinedType; + + const contextType = createClassMemberDecoratorContextTypeForNode(node, thisType, valueType); - function getTypeWithSyntheticDefaultImportType(type: Type, symbol: Symbol, originalSymbol: Symbol, moduleSpecifier: Expression): Type { - if (allowSyntheticDefaultImports && type && !isErrorType(type)) { - const synthType = type as SyntheticDefaultModuleType; - if (!synthType.syntheticType) { - const file = originalSymbol.declarations?.find(isSourceFile); - const hasSyntheticDefault = canHaveSyntheticDefault(file, originalSymbol, /*dontResolveAlias*/ false, moduleSpecifier); - if (hasSyntheticDefault) { - const anonymousSymbol = createSymbol(SymbolFlags.TypeLiteral, InternalSymbolName.Type); - const defaultContainingObject = createDefaultPropertyWrapperForModule(symbol, originalSymbol, anonymousSymbol); - anonymousSymbol.links.type = defaultContainingObject; - synthType.syntheticType = isValidSpreadType(type) ? getSpreadType(type, defaultContainingObject, anonymousSymbol, /*objectFlags*/ 0, /*readonly*/ false) : defaultContainingObject; - } - else { - synthType.syntheticType = type; + // We wrap the "output type" depending on the declaration. For auto-accessors, we wrap the + // "output type" in a `ClassAccessorDecoratorResult` type, which allows for + // mutation of the runtime-generated getter and setter, as well as the injection of an + // initializer mutator. For regular fields, we wrap the "output type" in an initializer mutator. + const returnType = + hasAccessorModifier(node) ? createClassAccessorDecoratorResultType(thisType, valueType) : + createClassFieldDecoratorInitializerMutatorType(thisType, valueType); + + links.decoratorSignature = createESDecoratorCallSignature(targetType, contextType, returnType); + break; } } - return synthType.syntheticType; } - return type; + return links.decoratorSignature === anySignature ? undefined : links.decoratorSignature; } - function isCommonJsRequire(node: Node): boolean { - if (!isRequireCall(node, /*requireStringLiteralLikeArgument*/ true)) { - return false; - } + function getLegacyDecoratorCallSignature(decorator: Decorator) { + const { parent } = decorator; + const links = getNodeLinks(parent); + if (!links.decoratorSignature) { + links.decoratorSignature = anySignature; + switch (parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: { + const node = parent as ClassDeclaration | ClassExpression; + // For a class decorator, the `target` is the type of the class (e.g. the + // "static" or "constructor" side of the class). + const targetType = getTypeOfSymbol(getSymbolOfDeclaration(node)); + const targetParam = createParameter("target" as __String, targetType); + links.decoratorSignature = createCallSignature( + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [targetParam], + getUnionType([targetType, voidType]) + ); + break; + } + case SyntaxKind.Parameter: { + const node = parent as ParameterDeclaration; + if (!isConstructorDeclaration(node.parent) && + !((isMethodDeclaration(node.parent) || isSetAccessorDeclaration(node.parent) && isClassLike(node.parent.parent)))) { + break; + } - // 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; - } + if (getThisParameter(node.parent) === node) { + break; + } - 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; - } + const index = getThisParameter(node.parent) ? + node.parent.parameters.indexOf(node) - 1 : + node.parent.parameters.indexOf(node); + Debug.assert(index >= 0); - 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); - } + // A parameter declaration decorator will have three arguments (see `ParameterDecorator` in + // core.d.ts). - function checkAssertion(node: AssertionExpression, checkMode: CheckMode | undefined) { - if (node.kind === SyntaxKind.TypeAssertionExpression) { - const file = getSourceFileOfNode(node); - if (file && fileExtensionIsOneOf(file.fileName, [Extension.Cts, Extension.Mts])) { - grammarErrorOnNode(node, Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Use_an_as_expression_instead); - } - } - return checkAssertionWorker(node, checkMode); - } + const targetType = + isConstructorDeclaration(node.parent) ? getTypeOfSymbol(getSymbolOfDeclaration(node.parent.parent)) : + getParentTypeOfClassElement(node.parent); - 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 as ParenthesizedExpression).expression); - case SyntaxKind.PrefixUnaryExpression: - const op = (node as PrefixUnaryExpression).operator; - const arg = (node as PrefixUnaryExpression).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 = skipParentheses((node as PropertyAccessExpression | ElementAccessExpression).expression); - const symbol = isEntityNameExpression(expr) ? resolveEntityName(expr, SymbolFlags.Value, /*ignoreErrors*/ true) : undefined; - return !!(symbol && symbol.flags & SymbolFlags.Enum); - } - return false; - } + const keyType = + isConstructorDeclaration(node.parent) ? undefinedType : + getClassElementPropertyKeyType(node.parent); - function checkAssertionWorker(node: JSDocTypeAssertion | AssertionExpression, checkMode: CheckMode | undefined) { - const { type, expression } = getAssertionTypeAndExpression(node); - const 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); - checkNodeDeferred(node); - return getTypeFromTypeNode(type); - } + const indexType = getNumberLiteralType(index); - function getAssertionTypeAndExpression(node: JSDocTypeAssertion | AssertionExpression) { - let type: TypeNode; - let expression: Expression; - switch (node.kind) { - case SyntaxKind.AsExpression: - case SyntaxKind.TypeAssertionExpression: - type = node.type; - expression = node.expression; - break; - case SyntaxKind.ParenthesizedExpression: - type = getJSDocTypeAssertionType(node); - expression = node.expression; - break; - } + const targetParam = createParameter("target" as __String, targetType); + const keyParam = createParameter("propertyKey" as __String, keyType); + const indexParam = createParameter("parameterIndex" as __String, indexType); + links.decoratorSignature = createCallSignature( + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [targetParam, keyParam, indexParam], + voidType + ); + break; + } + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.PropertyDeclaration: { + const node = parent as MethodDeclaration | AccessorDeclaration | PropertyDeclaration; + if (!isClassLike(node.parent)) break; - return { type, expression }; - } + // A method or accessor declaration decorator will have either 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. - function checkAssertionDeferred(node: JSDocTypeAssertion | AssertionExpression) { - const { type, expression } = getAssertionTypeAndExpression(node); - const errNode = isParenthesizedExpression(node) ? type : node; - const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(checkExpression(expression))); - const targetType = getTypeFromTypeNode(type); - if (!isErrorType(targetType)) { - addLazyDiagnostic(() => { - 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); + const targetType = getParentTypeOfClassElement(node); + const targetParam = createParameter("target" as __String, targetType); + + const keyType = getClassElementPropertyKeyType(node); + const keyParam = createParameter("propertyKey" as __String, keyType); + + const returnType = + isPropertyDeclaration(node) ? voidType : + createTypedPropertyDescriptorType(getTypeOfNode(node)); + + const hasPropDesc = languageVersion !== ScriptTarget.ES3 && (!isPropertyDeclaration(parent) || hasAccessorModifier(parent)); + if (hasPropDesc) { + const descriptorType = createTypedPropertyDescriptorType(getTypeOfNode(node)); + const descriptorParam = createParameter("descriptor" as __String, descriptorType); + links.decoratorSignature = createCallSignature( + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [targetParam, keyParam, descriptorParam], + getUnionType([returnType, voidType]) + ); + } + else { + links.decoratorSignature = createCallSignature( + /*typeParameters*/ undefined, + /*thisParameter*/ undefined, + [targetParam, keyParam], + getUnionType([returnType, voidType]) + ); + } + break; } - }); + } } + return links.decoratorSignature === anySignature ? undefined : links.decoratorSignature; } - function checkNonNullChain(node: NonNullChain) { - const leftType = checkExpression(node.expression); - const nonOptionalType = getOptionalExpressionType(leftType, node.expression); - return propagateOptionalTypeMarker(getNonNullableType(nonOptionalType), node, nonOptionalType !== leftType); + function getDecoratorCallSignature(decorator: Decorator) { + return legacyDecorators ? getLegacyDecoratorCallSignature(decorator) : + getESDecoratorCallSignature(decorator); } - function checkNonNullAssertion(node: NonNullExpression) { - return node.flags & NodeFlags.OptionalChain ? checkNonNullChain(node as NonNullChain) : - getNonNullableType(checkExpression(node.expression)); + 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 + // Unwrap an `Awaited` to `T` to improve inference. + promisedType = getAwaitedTypeNoAlias(unwrapAwaitedType(promisedType)) || unknownType; + return createTypeReference(globalPromiseType, [promisedType]); + } + + return unknownType; } - function checkExpressionWithTypeArguments(node: ExpressionWithTypeArguments | TypeQueryNode) { - checkGrammarExpressionWithTypeArguments(node); - forEach(node.typeArguments, checkSourceElement); - if (node.kind === SyntaxKind.ExpressionWithTypeArguments) { - const parent = walkUpParenthesizedExpressions(node.parent); - if (parent.kind === SyntaxKind.BinaryExpression && (parent as BinaryExpression).operatorToken.kind === SyntaxKind.InstanceOfKeyword && isNodeDescendantOf(node, (parent as BinaryExpression).right)) { - error(node, Diagnostics.The_right_hand_side_of_an_instanceof_expression_must_not_be_an_instantiation_expression); - } + 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 + // Unwrap an `Awaited` to `T` to improve inference. + promisedType = getAwaitedTypeNoAlias(unwrapAwaitedType(promisedType)) || unknownType; + return createTypeReference(globalPromiseLikeType, [promisedType]); } - const exprType = node.kind === SyntaxKind.ExpressionWithTypeArguments ? checkExpression(node.expression) : - isThisIdentifier(node.exprName) ? checkThisExpression(node.exprName) : - checkExpression(node.exprName); - return getInstantiationExpressionType(exprType, node); + + return unknownType; } - function getInstantiationExpressionType(exprType: Type, node: NodeWithTypeArguments) { - const typeArguments = node.typeArguments; - if (exprType === silentNeverType || isErrorType(exprType) || !some(typeArguments)) { - return exprType; + 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; } - let hasSomeApplicableSignature = false; - let nonApplicableType: Type | undefined; - const result = getInstantiatedType(exprType); - const errorType = hasSomeApplicableSignature ? nonApplicableType : exprType; - if (errorType) { - diagnostics.add(createDiagnosticForNodeArray(getSourceFileOfNode(node), typeArguments, Diagnostics.Type_0_has_no_signatures_for_which_the_type_argument_list_is_applicable, typeToString(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 result; - function getInstantiatedType(type: Type): Type { - let hasSignatures = false; - let hasApplicableSignature = false; - const result = getInstantiatedTypePart(type); - hasSomeApplicableSignature ||= hasApplicableSignature; - if (hasSignatures && !hasApplicableSignature) { - nonApplicableType ??= type; - } - return result; + return promiseType; + } - function getInstantiatedTypePart(type: Type): Type { - if (type.flags & TypeFlags.Object) { - const resolved = resolveStructuredTypeMembers(type as ObjectType); - const callSignatures = getInstantiatedSignatures(resolved.callSignatures); - const constructSignatures = getInstantiatedSignatures(resolved.constructSignatures); - hasSignatures ||= resolved.callSignatures.length !== 0 || resolved.constructSignatures.length !== 0; - hasApplicableSignature ||= callSignatures.length !== 0 || constructSignatures.length !== 0; - if (callSignatures !== resolved.callSignatures || constructSignatures !== resolved.constructSignatures) { - const result = createAnonymousType(/*symbol*/ undefined, resolved.members, callSignatures, constructSignatures, resolved.indexInfos) as ResolvedType & InstantiationExpressionType; - result.objectFlags |= ObjectFlags.InstantiationExpressionType; - result.node = node; - return result; - } - } - else if (type.flags & TypeFlags.InstantiableNonPrimitive) { - const constraint = getBaseConstraintOfType(type); - if (constraint) { - const instantiated = getInstantiatedTypePart(constraint); - if (instantiated !== constraint) { - return instantiated; - } - } - } - else if (type.flags & TypeFlags.Union) { - return mapType(type, getInstantiatedType); - } - else if (type.flags & TypeFlags.Intersection) { - return getIntersectionType(sameMap((type as IntersectionType).types, getInstantiatedTypePart)); - } - return type; - } - } + function createNewTargetExpressionType(targetType: Type): Type { + // Create a synthetic type `NewTargetExpression { target: TargetType; }` + const symbol = createSymbol(SymbolFlags.None, "NewTargetExpression" as __String); - function getInstantiatedSignatures(signatures: readonly Signature[]) { - const applicableSignatures = filter(signatures, sig => !!sig.typeParameters && hasCorrectTypeArgumentArity(sig, typeArguments)); - return sameMap(applicableSignatures, sig => { - const typeArgumentTypes = checkTypeArguments(sig, typeArguments!, /*reportErrors*/ true); - return typeArgumentTypes ? getSignatureInstantiation(sig, typeArgumentTypes, isInJSFile(sig.declaration)) : sig; - }); - } - } + const targetPropertySymbol = createSymbol(SymbolFlags.Property, "target" as __String, CheckFlags.Readonly); + targetPropertySymbol.parent = symbol; + targetPropertySymbol.links.type = targetType; - function checkSatisfiesExpression(node: SatisfiesExpression) { - checkSourceElement(node.type); - return checkSatisfiesExpressionWorker(node.expression, node.type); + const members = createSymbolTable([targetPropertySymbol]); + symbol.members = members; + return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); } - function checkSatisfiesExpressionWorker(expression: Expression, target: TypeNode, checkMode?: CheckMode) { - const exprType = checkExpression(expression, checkMode); - const targetType = getTypeFromTypeNode(target); - if (isErrorType(targetType)) { - return targetType; + function getReturnTypeFromBody(func: FunctionLikeDeclaration, checkMode?: CheckMode): Type { + if (!func.body) { + return errorType; } - const errorNode = findAncestor(target.parent, n => n.kind === SyntaxKind.SatisfiesExpression || n.kind === SyntaxKind.JSDocSatisfiesTag); - checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, errorNode, expression, Diagnostics.Type_0_does_not_satisfy_the_expected_type_1); - return exprType; - } - function checkMetaProperty(node: MetaProperty): Type { - checkGrammarMetaProperty(node); + const functionFlags = getFunctionFlags(func); + const isAsync = (functionFlags & FunctionFlags.Async) !== 0; + const isGenerator = (functionFlags & FunctionFlags.Generator) !== 0; - if (node.keywordToken === SyntaxKind.NewKeyword) { - return checkNewTargetMetaProperty(node); + 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 = unwrapAwaitedType(checkAwaitedType(returnType, /*withAlias*/ false, /*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/undefined, but rather a Promise for void/undefined. + const contextualReturnType = getContextualReturnType(func, /*contextFlags*/ undefined); + const returnType = contextualReturnType && (unwrapReturnType(contextualReturnType, functionFlags) || voidType).flags & TypeFlags.Undefined ? undefinedType : voidType; + return functionFlags & FunctionFlags.Async ? createPromiseReturnType(func, returnType) : // Async function + returnType; // Normal function + } - if (node.keywordToken === SyntaxKind.ImportKeyword) { - return checkImportMetaProperty(node); + // Return a union of the return expression types. + returnType = getUnionType(types, UnionReduction.Subtype); } - return Debug.assertNever(node.keywordToken); - } + 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, /*contextFlags*/ undefined); + 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); + } + } - function checkMetaPropertyKeyword(node: MetaProperty): Type { - switch (node.keywordToken) { - case SyntaxKind.ImportKeyword: - return getGlobalImportMetaExpressionType(); - case SyntaxKind.NewKeyword: - const type = checkNewTargetMetaProperty(node); - return isErrorType(type) ? errorType : createNewTargetExpressionType(type); - default: - Debug.assertNever(node.keywordToken); + if (yieldType) yieldType = getWidenedType(yieldType); + if (returnType) returnType = getWidenedType(returnType); + if (nextType) nextType = getWidenedType(nextType); } - } - 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 = getSymbolOfDeclaration(container.parent); - return getTypeOfSymbol(symbol); + if (isGenerator) { + return createGeneratorReturnType( + yieldType || neverType, + returnType || fallbackReturnType, + nextType || getContextualIterationType(IterationTypeKind.Next, func) || unknownType, + isAsync); } else { - const symbol = getSymbolOfDeclaration(container)!; - return getTypeOfSymbol(symbol); + // 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 checkImportMetaProperty(node: MetaProperty) { - if (moduleKind === ModuleKind.Node16 || moduleKind === ModuleKind.NodeNext) { - if (getSourceFileOfNode(node).impliedNodeFormat !== ModuleKind.ESNext) { - error(node, Diagnostics.The_import_meta_meta_property_is_not_allowed_in_files_which_will_build_into_CommonJS_output); + 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; } - else if (moduleKind < ModuleKind.ES2020 && moduleKind !== ModuleKind.System) { - error(node, Diagnostics.The_import_meta_meta_property_is_only_allowed_when_the_module_option_is_es2020_es2022_esnext_system_node16_or_nodenext); - } - const file = getSourceFileOfNode(node); - Debug.assert(!!(file.flags & NodeFlags.PossiblyContainsImportMeta), "Containing file is missing import meta node flag."); - return node.name.escapedText === "meta" ? getGlobalImportMetaType() : errorType; + + return createTypeFromGenericGlobalType(globalGeneratorType, [yieldType, returnType, nextType]); } - function getTypeOfParameter(symbol: Symbol) { - const type = getTypeOfSymbol(symbol); - if (strictNullChecks) { - const declaration = symbol.valueDeclaration; - if (declaration && hasInitializer(declaration)) { - return getOptionalType(type); + function checkAndAggregateYieldOperandTypes(func: FunctionLikeDeclaration, checkMode: CheckMode | undefined) { + const yieldTypes: Type[] = []; + const nextTypes: Type[] = []; + const isAsync = (getFunctionFlags(func) & FunctionFlags.Async) !== 0; + forEachYieldExpression(func.body as Block, 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; } - } - return type; + else { + nextType = getContextualType(yieldExpression, /*contextFlags*/ undefined); + } + if (nextType) pushIfUnique(nextTypes, nextType); + }); + return { yieldTypes, nextTypes }; } - 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 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); } - 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 as TypeReference).target as TupleType).labeledElementDeclarations; - const index = pos - paramCount; - return associatedNames && getTupleElementLabel(associatedNames[index]) || restParameter.escapedName + "_" + index as __String; + // Return the combined not-equal type facts for all cases except those between the start and end indices. + function getNotEqualFactsFromTypeofSwitch(start: number, end: number, witnesses: (string | undefined)[]): TypeFacts { + let facts: TypeFacts = TypeFacts.None; + for (let i = 0; i < witnesses.length; i++) { + const witness = i < start || i >= end ? witnesses[i] : undefined; + facts |= witness !== undefined ? typeofNEFacts.get(witness) || TypeFacts.TypeofNEHostObject : 0; } - return restParameter.escapedName; + return facts; } - function getParameterIdentifierNameAtPosition(signature: Signature, pos: number): [parameterName: __String, isRestParameter: boolean] | undefined { - if (signature.declaration?.kind === SyntaxKind.JSDocFunctionType) { - return undefined; + function isExhaustiveSwitchStatement(node: SwitchStatement): boolean { + const links = getNodeLinks(node); + if (links.isExhaustive === undefined) { + links.isExhaustive = 0; // Indicate resolution is in process + const exhaustive = computeExhaustiveSwitchStatement(node); + if (links.isExhaustive === 0) { + links.isExhaustive = exhaustive; + } } - const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); - if (pos < paramCount) { - const param = signature.parameters[pos]; - return isParameterDeclarationWithIdentifierName(param) ? [param.escapedName, false] : undefined; + else if (links.isExhaustive === 0) { + links.isExhaustive = false; // Resolve circularity to false } + return links.isExhaustive; + } - const restParameter = signature.parameters[paramCount] || unknownSymbol; - if (!isParameterDeclarationWithIdentifierName(restParameter)) { - return undefined; + function computeExhaustiveSwitchStatement(node: SwitchStatement): boolean { + if (node.expression.kind === SyntaxKind.TypeOfExpression) { + const witnesses = getSwitchClauseTypeOfWitnesses(node); + if (!witnesses) { + return false; + } + const operandConstraint = getBaseConstraintOrType(checkExpressionCached((node.expression as TypeOfExpression).expression)); + // Get the not-equal flags for all handled cases. + const notEqualFacts = getNotEqualFactsFromTypeofSwitch(0, 0, witnesses); + if (operandConstraint.flags & TypeFlags.AnyOrUnknown) { + // We special case the top types to be exhaustive when all cases are handled. + return (TypeFacts.AllTypeofNE & notEqualFacts) === TypeFacts.AllTypeofNE; + } + // A missing not-equal flag indicates that the type wasn't handled by some case. + return !someType(operandConstraint, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts); } - - const restType = getTypeOfSymbol(restParameter); - if (isTupleType(restType)) { - const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; - const index = pos - paramCount; - const associatedName = associatedNames?.[index]; - const isRestTupleElement = !!associatedName?.dotDotDotToken; - return associatedName ? [ - getTupleElementLabel(associatedName), - isRestTupleElement - ] : undefined; + const type = checkExpressionCached(node.expression); + if (!isLiteralType(type)) { + return false; } - - if (pos === paramCount) { - return [restParameter.escapedName, true]; + const switchTypes = getSwitchClauseTypes(node); + if (!switchTypes.length || some(switchTypes, isNeitherUnitTypeNorNever)) { + return false; } - return undefined; + return eachTypeContainedIn(mapType(type, getRegularTypeOfLiteralType), switchTypes); } - function isParameterDeclarationWithIdentifierName(symbol: Symbol) { - return symbol.valueDeclaration && isParameter(symbol.valueDeclaration) && isIdentifier(symbol.valueDeclaration.name); - } - function isValidDeclarationForTupleLabel(d: Declaration): d is NamedTupleMember | (ParameterDeclaration & { name: Identifier }) { - return d.kind === SyntaxKind.NamedTupleMember || (isParameter(d) && d.name && isIdentifier(d.name)); + function functionHasImplicitReturn(func: FunctionLikeDeclaration) { + return func.endFlowNode && isReachableFlowNode(func.endFlowNode); } - 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; + /** 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 as Block, 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 = unwrapAwaitedType(checkAwaitedType(type, /*withAlias*/ false, 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; } - const restParameter = signature.parameters[paramCount] || unknownSymbol; - const restType = getTypeOfSymbol(restParameter); - if (isTupleType(restType)) { - const associatedNames = ((restType as TypeReference).target as TupleType).labeledElementDeclarations; - const index = pos - paramCount; - return associatedNames && associatedNames[index]; + 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 restParameter.valueDeclaration && isValidDeclarationForTupleLabel(restParameter.valueDeclaration) ? restParameter.valueDeclaration : undefined; + return aggregatedTypes; } - - function getTypeAtPosition(signature: Signature, pos: number): Type { - return tryGetTypeAtPosition(signature, pos) || anyType; + 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; + } } - 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, getNumberLiteralType(index)); + /** + * 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) { + addLazyDiagnostic(checkAllCodePathsInNonVoidFunctionReturnOrThrowDiagnostics); + return; + + function checkAllCodePathsInNonVoidFunctionReturnOrThrowDiagnostics(): void { + const functionFlags = getFunctionFlags(func); + const type = returnType && unwrapReturnType(returnType, functionFlags); + + // Functions with an explicitly specified return type that includes `void` or is exactly `any` or `undefined` don't + // need any return statements. + if (type && (maybeTypeOfKind(type, TypeFlags.Void) || type.flags & (TypeFlags.Any | TypeFlags.Undefined))) { + return; } - } - 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); + // 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; } - else { - types.push(restType); - flags.push(ElementFlags.Variadic); + + const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn; + const errorNode = getEffectiveReturnTypeNode(func) || func; + + if (type && type.flags & TypeFlags.Never) { + error(errorNode, Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point); } - const name = getNameableDeclarationAtPosition(source, i); - if (name) { - names.push(name); + 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. + error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_undefined_void_nor_any_must_return_a_value); + } + else if (type && strictNullChecks && !isTypeAssignableTo(undefinedType, type)) { + error(errorNode, 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 (isUnwrappedReturnTypeUndefinedVoidOrAny(func, inferredReturnType)) { + return; + } + } + error(errorNode, Diagnostics.Not_all_code_paths_return_a_value); } } - return createTupleType(types, flags, /*readonly*/ false, length(names) === length(types) ? names : undefined); } - // Return the number of parameters in a signature. The rest parameter, if present, counts as one - // parameter. For example, the parameter count of (x: number, y: number, ...z: string[]) is 3 and - // the parameter count of (x: number, ...args: [number, ...string[], boolean])) is also 3. In the - // latter example, the effective rest type is [...string[], boolean]. - 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); - } + function checkFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode): Type { + Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); + checkNodeDeferred(node); + + if (isFunctionExpression(node)) { + checkCollisionsForDeclarationName(node, node.name); } - 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; + // 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(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.IsNonInferrable); + const returnOnlyType = createAnonymousType(node.symbol, emptySymbols, [returnOnlySignature], emptyArray, emptyArray); + returnOnlyType.objectFlags |= ObjectFlags.NonInferrableType; + return links.contextFreeType = returnOnlyType; } } - if (minArgumentCount === undefined) { - if (!strongArityForUntypedJS && signature.flags & SignatureFlags.IsUntypedSignatureInJSFile) { - return 0; + return anyFunctionType; + } + + // Grammar checking + const hasGrammarError = checkGrammarFunctionLikeDeclaration(node); + if (!hasGrammarError && node.kind === SyntaxKind.FunctionExpression) { + checkGrammarForGenerator(node); + } + + contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node, checkMode); + + return getTypeOfSymbol(getSymbolOfDeclaration(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(getSymbolOfDeclaration(node)), SignatureKind.Call)); + if (!signature) { + return; } - minArgumentCount = signature.minArgumentCount; - } - for (let i = 0; i < signature.parameters.length; i++) { - if (signature.parameters[i].valueDeclaration && (signature.parameters[i].valueDeclaration as ParameterDeclaration).isAuto) { - minArgumentCount = signature.parameters.length - (signature.parameters.length - i); - break; + if (isContextSensitive(node)) { + if (contextualSignature) { + const inferenceContext = getInferenceContext(node); + let instantiatedContextualSignature: Signature | undefined; + if (checkMode && checkMode & CheckMode.Inferential) { + inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); + const restType = getEffectiveRestType(contextualSignature); + if (restType && restType.flags & TypeFlags.TypeParameter) { + instantiatedContextualSignature = instantiateSignature(contextualSignature, inferenceContext!.nonFixingMapper); + } + } + 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 (voidIsNonOptional) { - return minArgumentCount; - } - for (let i = minArgumentCount - 1; i >= 0; i--) { - const type = getTypeAtPosition(signature, i); - if (filterType(type, acceptsVoid).flags & TypeFlags.Never) { - break; + else if (contextualSignature && !node.typeParameters && contextualSignature.parameters.length > node.parameters.length) { + const inferenceContext = getInferenceContext(node); + if (checkMode && checkMode & CheckMode.Inferential) { + inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); + } } - minArgumentCount = i; + if (contextualSignature && !getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) { + const returnType = getReturnTypeFromBody(node, checkMode); + if (!signature.resolvedReturnType) { + signature.resolvedReturnType = returnType; + } + } + checkSignatureDeclaration(node); } - 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 checkFunctionExpressionOrObjectLiteralMethodDeferred(node: ArrowFunction | FunctionExpression | MethodDeclaration) { + Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); - function getEffectiveRestType(signature: Signature) { - if (signatureHasRestParameter(signature)) { - const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); - if (!isTupleType(restType)) { - return restType; + 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 (restType.target.hasRestElement) { - return sliceTupleType(restType, restType.target.fixedLength); + + 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, /*withAlias*/ false, 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); + } + } } } - return undefined; - } - - function getNonArrayRestType(signature: Signature) { - const restType = getEffectiveRestType(signature); - return restType && !isArrayType(restType) && !isTypeAny(restType) ? 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 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 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 as ParameterDeclaration; - if (declaration.type) { - const typeNode = getEffectiveTypeAnnotationNode(declaration); - if (typeNode) { - inferTypes(inferenceContext.inferences, getTypeFromTypeNode(typeNode), getTypeAtPosition(context, i)); + 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 assignContextualParameterTypes(signature: Signature, context: Signature) { - if (context.typeParameters) { - if (!signature.typeParameters) { - signature.typeParameters = context.typeParameters; - } - else { - return; // This signature has already has a contextual inference performed and cached on it! - } + 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 (context.thisParameter) { - const parameter = signature.thisParameter; - if (!parameter || parameter.valueDeclaration && !(parameter.valueDeclaration as ParameterDeclaration).type) { - if (!parameter) { - signature.thisParameter = createSymbolWithType(context.thisParameter, /*type*/ undefined); + 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; } - 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 as ParameterDeclaration)) { - const contextualParameterType = tryGetTypeAtPosition(context, i); - assignParameterType(parameter, contextualParameterType); } + return true; } - if (signatureHasRestParameter(signature)) { - // parameter might be a transient symbol generated by use of `arguments` in the function body. - const parameter = last(signature.parameters); - if (parameter.valueDeclaration - ? !getEffectiveTypeAnnotationNode(parameter.valueDeclaration as ParameterDeclaration) - // a declarationless parameter may still have a `.type` already set by its construction logic - // (which may pull a type from a jsdoc) - only allow fixing on `DeferredType` parameters with a fallback type - : !!(getCheckFlags(parameter) & CheckFlags.DeferredType) - ) { - const contextualParameterType = getRestTypeAtPosition(context, len); - assignParameterType(parameter, contextualParameterType); + 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 assignNonContextualParameterTypes(signature: Signature) { - if (signature.thisParameter) { - assignParameterType(signature.thisParameter); + 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; } - for (const parameter of signature.parameters) { - assignParameterType(parameter); + if (node.flags & NodeFlags.OptionalChain) { + error(expr, invalidOptionalChainMessage); + return false; } + return true; } - function assignParameterType(parameter: Symbol, type?: Type) { - const links = getSymbolLinks(parameter); - if (!links.type) { - const declaration = parameter.valueDeclaration as ParameterDeclaration | undefined; - links.type = type || (declaration ? getWidenedTypeForVariableLikeDeclaration(declaration, /*reportErrors*/ true) : getTypeOfSymbol(parameter)); - if (declaration && 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, links.type); - } + 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; } - else if (type) { - Debug.assertEqual(links.type, type, "Parameter symbol already has a cached type which differs from newly assigned type"); + if (isPropertyAccessExpression(expr) && isPrivateIdentifier(expr.name)) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_private_identifier); } - } - - // 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, parentType: Type) { - for (const element of pattern.elements) { - if (!isOmittedExpression(element)) { - const type = getBindingElementTypeFromParentType(element, parentType); - if (element.name.kind === SyntaxKind.Identifier) { - getSymbolLinks(getSymbolOfDeclaration(element)).type = type; - } - else { - assignBindingElementTypes(element.name, type); - } + 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, symbol); } + return booleanType; } - function createClassDecoratorContextType(classType: Type) { - return tryCreateTypeReference(getGlobalClassDecoratorContextType(/*reportErrors*/ true), [classType]); - } - - function createClassMethodDecoratorContextType(thisType: Type, valueType: Type) { - return tryCreateTypeReference(getGlobalClassMethodDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); - } - - function createClassGetterDecoratorContextType(thisType: Type, valueType: Type) { - return tryCreateTypeReference(getGlobalClassGetterDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); - } - - function createClassSetterDecoratorContextType(thisType: Type, valueType: Type) { - return tryCreateTypeReference(getGlobalClassSetterDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); - } - - function createClassAccessorDecoratorContextType(thisType: Type, valueType: Type) { - return tryCreateTypeReference(getGlobalClassAccessorDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); - } - - function createClassFieldDecoratorContextType(thisType: Type, valueType: Type) { - return tryCreateTypeReference(getGlobalClassFieldDecoratorContextType(/*reportErrors*/ true), [thisType, valueType]); - } - - /** - * Gets a type like `{ name: "foo", private: false, static: true }` that is used to provided member-specific - * details that will be intersected with a decorator context type. - */ - function getClassMemberDecoratorContextOverrideType(nameType: Type, isPrivate: boolean, isStatic: boolean) { - const key = `${isPrivate ? "p" : "P"}${isStatic ? "s" : "S"}${nameType.id}` as const; - let overrideType = decoratorContextOverrideTypeCache.get(key); - if (!overrideType) { - const members = createSymbolTable(); - members.set("name" as __String, createProperty("name" as __String, nameType)); - members.set("private" as __String, createProperty("private" as __String, isPrivate ? trueType : falseType)); - members.set("static" as __String, createProperty("static" as __String, isStatic ? trueType : falseType)); - overrideType = createAnonymousType(/*symbol*/ undefined, members, emptyArray, emptyArray, emptyArray); - decoratorContextOverrideTypeCache.set(key, overrideType); + function checkDeleteExpressionMustBeOptional(expr: AccessExpression, symbol: Symbol) { + const type = getTypeOfSymbol(symbol); + if (strictNullChecks && + !(type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Never)) && + !(exactOptionalPropertyTypes ? symbol.flags & SymbolFlags.Optional : getTypeFacts(type) & TypeFacts.IsUndefined)) { + error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_optional); } - return overrideType; - } - - function createClassMemberDecoratorContextTypeForNode(node: MethodDeclaration | AccessorDeclaration | PropertyDeclaration, thisType: Type, valueType: Type) { - const isStatic = hasStaticModifier(node); - const isPrivate = isPrivateIdentifier(node.name); - const nameType = isPrivate ? getStringLiteralType(idText(node.name)) : getLiteralTypeFromPropertyName(node.name); - const contextType = - isMethodDeclaration(node) ? createClassMethodDecoratorContextType(thisType, valueType) : - isGetAccessorDeclaration(node) ? createClassGetterDecoratorContextType(thisType, valueType) : - isSetAccessorDeclaration(node) ? createClassSetterDecoratorContextType(thisType, valueType) : - isAutoAccessorPropertyDeclaration(node) ? createClassAccessorDecoratorContextType(thisType, valueType) : - isPropertyDeclaration(node) ? createClassFieldDecoratorContextType(thisType, valueType) : - Debug.failBadSyntaxKind(node); - const overrideType = getClassMemberDecoratorContextOverrideType(nameType, isPrivate, isStatic); - return getIntersectionType([contextType, overrideType]); - - } - - function createClassAccessorDecoratorTargetType(thisType: Type, valueType: Type) { - return tryCreateTypeReference(getGlobalClassAccessorDecoratorTargetType(/*reportErrors*/ true), [thisType, valueType]); - } - - function createClassAccessorDecoratorResultType(thisType: Type, valueType: Type) { - return tryCreateTypeReference(getGlobalClassAccessorDecoratorResultType(/*reportErrors*/ true), [thisType, valueType]); } - function createClassFieldDecoratorInitializerMutatorType(thisType: Type, valueType: Type) { - const thisParam = createParameter("this" as __String, thisType); - const valueParam = createParameter("value" as __String, valueType); - return createFunctionType(/*typeParameters*/ undefined, thisParam, [valueParam], valueType, /*typePredicate*/ undefined, 1); + function checkTypeOfExpression(node: TypeOfExpression): Type { + checkExpression(node.expression); + return typeofType; } - /** - * Creates a call signature for an ES Decorator. This method is used by the semantics of - * `getESDecoratorCallSignature`, which you should probably be using instead. - */ - function createESDecoratorCallSignature(targetType: Type, contextType: Type, nonOptionalReturnType: Type) { - const targetParam = createParameter("target" as __String, targetType); - const contextParam = createParameter("context" as __String, contextType); - const returnType = getUnionType([nonOptionalReturnType, voidType]); - return createCallSignature(/*typeParameters*/ undefined, /*thisParameter*/ undefined, [targetParam, contextParam], returnType); + function checkVoidExpression(node: VoidExpression): Type { + checkExpression(node.expression); + return undefinedWideningType; } - /** - * Gets a call signature that should be used when resolving `decorator` as a call. This does not use the value - * of the decorator itself, but instead uses the declaration on which it is placed along with its relative - * position amongst other decorators on the same declaration to determine the applicable signature. The - * resulting signature can be used for call resolution, inference, and contextual typing. - */ - function getESDecoratorCallSignature(decorator: Decorator) { - // We are considering a future change that would allow the type of a decorator to affect the type of the - // class and its members, such as a `@Stringify` decorator changing the type of a `number` field to `string`, or - // a `@Callable` decorator adding a call signature to a `class`. The type arguments for the various context - // types may eventually change to reflect such mutations. - // - // In some cases we describe such potential mutations as coming from a "prior decorator application". It is - // important to note that, while decorators are *evaluated* left to right, they are *applied* right to left - // to preserve f ৹ g -> f(g(x)) application order. In these cases, a "prior" decorator usually means the - // next decorator following this one in document order. - // - // The "original type" of a class or member is the type it was declared as, or the type we infer from - // initializers, before _any_ decorators are applied. - // - // The type of a class or member that is a result of a prior decorator application represents the - // "current type", i.e., the type for the declaration at the time the decorator is _applied_. - // - // The type of a class or member that is the result of the application of *all* relevant decorators is the - // "final type". - // - // Any decorator that allows mutation or replacement will also refer to an "input type" and an - // "output type". The "input type" corresponds to the "current type" of the declaration, while the - // "output type" will become either the "input type/current type" for a subsequent decorator application, - // or the "final type" for the decorated declaration. - // - // It is important to understand decorator application order as it relates to how the "current", "input", - // "output", and "final" types will be determined: - // - // @E2 @E1 class SomeClass { - // @A2 @A1 static f() {} - // @B2 @B1 g() {} - // @C2 @C1 static x; - // @D2 @D1 y; - // } - // - // Per [the specification][1], decorators are applied in the following order: - // - // 1. For each static method (incl. get/set methods and `accessor` fields), in document order: - // a. Apply each decorator for that method, in reverse order (`A1`, `A2`). - // 2. For each instance method (incl. get/set methods and `accessor` fields), in document order: - // a. Apply each decorator for that method, in reverse order (`B1`, `B2`). - // 3. For each static field (excl. auto-accessors), in document order: - // a. Apply each decorator for that field, in reverse order (`C1`, `C2`). - // 4. For each instance field (excl. auto-accessors), in document order: - // a. Apply each decorator for that field, in reverse order (`D1`, `D2`). - // 5. Apply each decorator for the class, in reverse order (`E1`, `E2`). - // - // As a result, "current" types at each decorator application are as follows: - // - For `A1`, the "current" types of the class and method are their "original" types. - // - For `A2`, the "current type" of the method is the "output type" of `A1`, and the "current type" of the - // class is the type of `SomeClass` where `f` is the "output type" of `A1`. This becomes the "final type" - // of `f`. - // - For `B1`, the "current type" of the method is its "original type", and the "current type" of the class - // is the type of `SomeClass` where `f` now has its "final type". - // - etc. - // - // [1]: https://arai-a.github.io/ecma262-compare/?pr=2417&id=sec-runtime-semantics-classdefinitionevaluation - // - // This seems complicated at first glance, but is not unlike our existing inference for functions: - // - // declare function pipe( - // original: Original, - // a1: (input: Original, context: Context) => A1, - // a2: (input: A1, context: Context) => A2, - // b1: (input: A2, context: Context) => B1, - // b2: (input: B1, context: Context) => B2, - // c1: (input: B2, context: Context) => C1, - // c2: (input: C1, context: Context) => C2, - // d1: (input: C2, context: Context) => D1, - // d2: (input: D1, context: Context) => D2, - // e1: (input: D2, context: Context) => E1, - // e2: (input: E1, context: Context) => E2, - // ): E2; - - // When a decorator is applied, it is passed two arguments: "target", which is a value representing the - // thing being decorated (constructors for classes, functions for methods/accessors, `undefined` for fields, - // and a `{ get, set }` object for auto-accessors), and "context", which is an object that provides - // reflection information about the decorated element, as well as the ability to add additional "extra" - // initializers. In most cases, the "target" argument corresponds to the "input type" in some way, and the - // return value similarly corresponds to the "output type" (though if the "output type" is `void` or - // `undefined` then the "output type" is the "input type"). - - const { parent } = decorator; - const links = getNodeLinks(parent); - if (!links.decoratorSignature) { - links.decoratorSignature = anySignature; - switch (parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: { - // Class decorators have a `context` of `ClassDecoratorContext`, where the `Class` type - // argument will be the "final type" of the class after all decorators are applied. - - const node = parent as ClassDeclaration | ClassExpression; - const targetType = getTypeOfSymbol(getSymbolOfDeclaration(node)); - const contextType = createClassDecoratorContextType(targetType); - links.decoratorSignature = createESDecoratorCallSignature(targetType, contextType, targetType); - break; - } - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: { - const node = parent as MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; - if (!isClassLike(node.parent)) break; - - // Method decorators have a `context` of `ClassMethodDecoratorContext`, where the - // `Value` type argument corresponds to the "final type" of the method. - // - // Getter decorators have a `context` of `ClassGetterDecoratorContext`, where the - // `Value` type argument corresponds to the "final type" of the value returned by the getter. - // - // Setter decorators have a `context` of `ClassSetterDecoratorContext`, where the - // `Value` type argument corresponds to the "final type" of the parameter of the setter. - // - // In all three cases, the `This` type argument is the "final type" of either the class or - // instance, depending on whether the member was `static`. - - const valueType = - isMethodDeclaration(node) ? getOrCreateTypeFromSignature(getSignatureFromDeclaration(node)) : - getTypeOfNode(node); - - const thisType = hasStaticModifier(node) ? - getTypeOfSymbol(getSymbolOfDeclaration(node.parent)) : - getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(node.parent)); - - // We wrap the "input type", if necessary, to match the decoration target. For getters this is - // something like `() => inputType`, for setters it's `(value: inputType) => void` and for - // methods it is just the input type. - const targetType = - isGetAccessorDeclaration(node) ? createGetterFunctionType(valueType) : - isSetAccessorDeclaration(node) ? createSetterFunctionType(valueType) : - valueType; - - const contextType = createClassMemberDecoratorContextTypeForNode(node, thisType, valueType); - - // We also wrap the "output type", as needed. - const returnType = - isGetAccessorDeclaration(node) ? createGetterFunctionType(valueType) : - isSetAccessorDeclaration(node) ? createSetterFunctionType(valueType) : - valueType; - - links.decoratorSignature = createESDecoratorCallSignature(targetType, contextType, returnType); - break; - } - - case SyntaxKind.PropertyDeclaration: { - const node = parent as PropertyDeclaration; - if (!isClassLike(node.parent)) break; - - // Field decorators have a `context` of `ClassFieldDecoratorContext` and - // auto-accessor decorators have a `context` of `ClassAccessorDecoratorContext. In - // both cases, the `This` type argument is the "final type" of either the class or instance, - // depending on whether the member was `static`, and the `Value` type argument corresponds to - // the "final type" of the value stored in the field. - - const valueType = getTypeOfNode(node); - const thisType = hasStaticModifier(node) ? - getTypeOfSymbol(getSymbolOfDeclaration(node.parent)) : - getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(node.parent)); - - // The `target` of an auto-accessor decorator is a `{ get, set }` object, representing the - // runtime-generated getter and setter that are added to the class/prototype. The `target` of a - // regular field decorator is always `undefined` as it isn't installed until it is initialized. - const targetType = - hasAccessorModifier(node) ? createClassAccessorDecoratorTargetType(thisType, valueType) : - undefinedType; - - const contextType = createClassMemberDecoratorContextTypeForNode(node, thisType, valueType); - - // We wrap the "output type" depending on the declaration. For auto-accessors, we wrap the - // "output type" in a `ClassAccessorDecoratorResult` type, which allows for - // mutation of the runtime-generated getter and setter, as well as the injection of an - // initializer mutator. For regular fields, we wrap the "output type" in an initializer mutator. - const returnType = - hasAccessorModifier(node) ? createClassAccessorDecoratorResultType(thisType, valueType) : - createClassFieldDecoratorInitializerMutatorType(thisType, valueType); - - links.decoratorSignature = createESDecoratorCallSignature(targetType, contextType, returnType); - break; - } - } + function checkAwaitExpressionGrammar(node: AwaitExpression): void { + // Grammar checking + const container = getContainingFunctionOrClassStaticBlock(node); + if (container && isClassStaticBlockDeclaration(container)) { + error(node, Diagnostics.Await_expression_cannot_be_used_inside_a_class_static_block); } - return links.decoratorSignature === anySignature ? undefined : links.decoratorSignature; - } - - function getLegacyDecoratorCallSignature(decorator: Decorator) { - const { parent } = decorator; - const links = getNodeLinks(parent); - if (!links.decoratorSignature) { - links.decoratorSignature = anySignature; - switch (parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: { - const node = parent as ClassDeclaration | ClassExpression; - // For a class decorator, the `target` is the type of the class (e.g. the - // "static" or "constructor" side of the class). - const targetType = getTypeOfSymbol(getSymbolOfDeclaration(node)); - const targetParam = createParameter("target" as __String, targetType); - links.decoratorSignature = createCallSignature( - /*typeParameters*/ undefined, - /*thisParameter*/ undefined, - [targetParam], - getUnionType([targetType, voidType]) - ); - break; - } - case SyntaxKind.Parameter: { - const node = parent as ParameterDeclaration; - if (!isConstructorDeclaration(node.parent) && - !((isMethodDeclaration(node.parent) || isSetAccessorDeclaration(node.parent) && isClassLike(node.parent.parent)))) { - break; + else if (!(node.flags & NodeFlags.AwaitContext)) { + if (isInTopLevelContext(node)) { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + let span: TextSpan | undefined; + if (!isEffectiveExternalModule(sourceFile, compilerOptions)) { + 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 (getThisParameter(node.parent) === node) { - break; + switch (moduleKind) { + case ModuleKind.Node16: + case ModuleKind.NodeNext: + if (sourceFile.impliedNodeFormat === ModuleKind.CommonJS) { + span ??= getSpanOfTokenAtPosition(sourceFile, node.pos); + diagnostics.add( + createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.The_current_file_is_a_CommonJS_module_and_cannot_use_await_at_the_top_level) + ); + break; + } + // fallthrough + case ModuleKind.ES2022: + case ModuleKind.ESNext: + case ModuleKind.System: + if (languageVersion >= ScriptTarget.ES2017) { + break; + } + // fallthrough + default: + span ??= getSpanOfTokenAtPosition(sourceFile, node.pos); + diagnostics.add( + createFileDiagnostic(sourceFile, span.start, span.length, + Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher + ) + ); + break; + } + } + } + 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); + if (container && container.kind !== SyntaxKind.Constructor && (getFunctionFlags(container) & FunctionFlags.Async) === 0) { + const relatedInfo = createDiagnosticForNode(container, Diagnostics.Did_you_mean_to_mark_this_function_as_async); + addRelatedInfo(diagnostic, relatedInfo); } - - const index = getThisParameter(node.parent) ? - node.parent.parameters.indexOf(node) - 1 : - node.parent.parameters.indexOf(node); - Debug.assert(index >= 0); - - // A parameter declaration decorator will have three arguments (see `ParameterDecorator` in - // core.d.ts). - - const targetType = - isConstructorDeclaration(node.parent) ? getTypeOfSymbol(getSymbolOfDeclaration(node.parent.parent)) : - getParentTypeOfClassElement(node.parent); - - const keyType = - isConstructorDeclaration(node.parent) ? undefinedType : - getClassElementPropertyKeyType(node.parent); - - const indexType = getNumberLiteralType(index); - - const targetParam = createParameter("target" as __String, targetType); - const keyParam = createParameter("propertyKey" as __String, keyType); - const indexParam = createParameter("parameterIndex" as __String, indexType); - links.decoratorSignature = createCallSignature( - /*typeParameters*/ undefined, - /*thisParameter*/ undefined, - [targetParam, keyParam, indexParam], - voidType - ); - break; + diagnostics.add(diagnostic); } - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.PropertyDeclaration: { - const node = parent as MethodDeclaration | AccessorDeclaration | PropertyDeclaration; - if (!isClassLike(node.parent)) break; - - // A method or accessor declaration decorator will have either 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 targetType = getParentTypeOfClassElement(node); - const targetParam = createParameter("target" as __String, targetType); + if (isInParameterInitializerBeforeContainingFunction(node)) { + error(node, Diagnostics.await_expressions_cannot_be_used_in_a_parameter_initializer); + } + } - const keyType = getClassElementPropertyKeyType(node); - const keyParam = createParameter("propertyKey" as __String, keyType); + function checkAwaitExpression(node: AwaitExpression): Type { + addLazyDiagnostic(() => checkAwaitExpressionGrammar(node)); - const returnType = - isPropertyDeclaration(node) ? voidType : - createTypedPropertyDescriptorType(getTypeOfNode(node)); + const operandType = checkExpression(node.expression); + const awaitedType = checkAwaitedType(operandType, /*withAlias*/ true, node, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + if (awaitedType === operandType && !isErrorType(awaitedType) && !(operandType.flags & TypeFlags.AnyOrUnknown)) { + addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.await_has_no_effect_on_the_type_of_this_expression)); + } + return awaitedType; + } - const hasPropDesc = languageVersion !== ScriptTarget.ES3 && (!isPropertyDeclaration(parent) || hasAccessorModifier(parent)); - if (hasPropDesc) { - const descriptorType = createTypedPropertyDescriptorType(getTypeOfNode(node)); - const descriptorParam = createParameter("descriptor" as __String, descriptorType); - links.decoratorSignature = createCallSignature( - /*typeParameters*/ undefined, - /*thisParameter*/ undefined, - [targetParam, keyParam, descriptorParam], - getUnionType([returnType, voidType]) - ); - } - else { - links.decoratorSignature = createCallSignature( - /*typeParameters*/ undefined, - /*thisParameter*/ undefined, - [targetParam, keyParam], - getUnionType([returnType, voidType]) - ); + 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(getNumberLiteralType(-(node.operand as NumericLiteral).text)); + case SyntaxKind.PlusToken: + return getFreshTypeOfLiteralType(getNumberLiteralType(+(node.operand as NumericLiteral).text)); + } + break; + case SyntaxKind.BigIntLiteral: + if (node.operator === SyntaxKind.MinusToken) { + return getFreshTypeOfLiteralType(getBigIntLiteralType({ + 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 (maybeTypeOfKindConsideringBaseConstraint(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 (maybeTypeOfKindConsideringBaseConstraint(operandType, TypeFlags.BigIntLike)) { + error(node.operand, Diagnostics.Operator_0_cannot_be_applied_to_type_1, tokenToString(node.operator), typeToString(getBaseTypeOfLiteralType(operandType))); } - break; + return numberType; } - } + return getUnaryResultType(operandType); + case SyntaxKind.ExclamationToken: + checkTruthinessOfType(operandType, 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 links.decoratorSignature === anySignature ? undefined : links.decoratorSignature; + return errorType; } - function getDecoratorCallSignature(decorator: Decorator) { - return legacyDecorators ? getLegacyDecoratorCallSignature(decorator) : - getESDecoratorCallSignature(decorator); + 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 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 - // Unwrap an `Awaited` to `T` to improve inference. - promisedType = getAwaitedTypeNoAlias(unwrapAwaitedType(promisedType)) || unknownType; - return createTypeReference(globalPromiseType, [promisedType]); + function getUnaryResultType(operandType: Type): Type { + if (maybeTypeOfKind(operandType, TypeFlags.BigIntLike)) { + return isTypeAssignableToKind(operandType, TypeFlags.AnyOrUnknown) || maybeTypeOfKind(operandType, TypeFlags.NumberLike) + ? numberOrBigIntType + : bigintType; } - - return unknownType; + // If it's not a bigint type, implicit coercion will result in a number + return numberType; } - 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 - // Unwrap an `Awaited` to `T` to improve inference. - promisedType = getAwaitedTypeNoAlias(unwrapAwaitedType(promisedType)) || unknownType; - return createTypeReference(globalPromiseLikeType, [promisedType]); + function maybeTypeOfKindConsideringBaseConstraint(type: Type, kind: TypeFlags): boolean { + if (maybeTypeOfKind(type, kind)) { + return true; } - return unknownType; + const baseConstraint = getBaseConstraintOrType(type); + return !!baseConstraint && maybeTypeOfKind(baseConstraint, kind); } - 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; + // 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; } - 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); + if (type.flags & TypeFlags.UnionOrIntersection) { + const types = (type as UnionOrIntersectionType).types; + for (const t of types) { + if (maybeTypeOfKind(t, kind)) { + return true; + } + } } + return false; + } - return promiseType; + 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 createNewTargetExpressionType(targetType: Type): Type { - // Create a synthetic type `NewTargetExpression { target: TargetType; }` - const symbol = createSymbol(SymbolFlags.None, "NewTargetExpression" as __String); + 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); + } - const targetPropertySymbol = createSymbol(SymbolFlags.Property, "target" as __String, CheckFlags.Readonly); - targetPropertySymbol.parent = symbol; - targetPropertySymbol.links.type = targetType; + function isConstEnumObjectType(type: Type): boolean { + return !!(getObjectFlags(type) & ObjectFlags.Anonymous) && !!type.symbol && isConstEnumSymbol(type.symbol); + } - const members = createSymbolTable([targetPropertySymbol]); - symbol.members = members; - return createAnonymousType(symbol, members, emptyArray, emptyArray, emptyArray); + function isConstEnumSymbol(symbol: Symbol): boolean { + return (symbol.flags & SymbolFlags.ConstEnum) !== 0; } - function getReturnTypeFromBody(func: FunctionLikeDeclaration, checkMode?: CheckMode): Type { - if (!func.body) { - return errorType; + 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; + } - const functionFlags = getFunctionFlags(func); - const isAsync = (functionFlags & FunctionFlags.Async) !== 0; - const isGenerator = (functionFlags & FunctionFlags.Generator) !== 0; + function hasEmptyObjectIntersection(type: Type): boolean { + return someType(type, t => t === unknownEmptyObjectType || !!(t.flags & TypeFlags.Intersection) && isEmptyAnonymousObjectType(getBaseConstraintOrType(t))); + } - 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 = unwrapAwaitedType(checkAwaitedType(returnType, /*withAlias*/ false, /*errorNode*/ func, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member)); - } + function checkInExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; } - else if (isGenerator) { // Generator or AsyncGenerator function - const returnTypes = checkAndAggregateReturnExpressionTypes(func, checkMode); - if (!returnTypes) { - fallbackReturnType = neverType; + if (isPrivateIdentifier(left)) { + if (languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(left, ExternalEmitHelpers.ClassPrivateFieldIn); } - else if (returnTypes.length > 0) { - returnType = getUnionType(returnTypes, UnionReduction.Subtype); + // Unlike in 'checkPrivateIdentifierExpression' we now have access to the RHS type + // which provides us with the opportunity to emit more detailed errors + if (!getNodeLinks(left).resolvedSymbol && getContainingClass(left)) { + const isUncheckedJS = isUncheckedJSSuggestion(left, rightType.symbol, /*excludeClasses*/ true); + reportNonexistentProperty(left, rightType, isUncheckedJS); } - 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/undefined, but rather a Promise for void/undefined. - const contextualReturnType = getContextualReturnType(func, /*contextFlags*/ undefined); - const returnType = contextualReturnType && (unwrapReturnType(contextualReturnType, functionFlags) || voidType).flags & TypeFlags.Undefined ? undefinedType : voidType; - return functionFlags & FunctionFlags.Async ? createPromiseReturnType(func, returnType) : // Async function - returnType; // Normal function - } - - // Return a union of the return expression types. - returnType = getUnionType(types, UnionReduction.Subtype); + else { + // The type of the lef operand must be assignable to string, number, or symbol. + checkTypeAssignableTo(checkNonNullType(leftType, left), stringNumberSymbolType, left); } - - 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, /*contextFlags*/ undefined); - 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); - } + // The type of the right operand must be assignable to 'object'. + if (checkTypeAssignableTo(checkNonNullType(rightType, right), nonPrimitiveType, right)) { + // The {} type is assignable to the object type, yet {} might represent a primitive type. Here we + // detect and error on {} that results from narrowing the unknown type, as well as intersections + // that include {} (we know that the other types in such intersections are assignable to object + // since we already checked for that). + if (hasEmptyObjectIntersection(rightType)) { + error(right, Diagnostics.Type_0_may_represent_a_primitive_value_which_is_not_permitted_as_the_right_operand_of_the_in_operator, typeToString(rightType)); } - - if (yieldType) yieldType = getWidenedType(yieldType); - if (returnType) returnType = getWidenedType(returnType); - if (nextType) nextType = getWidenedType(nextType); } + // The result is always of the Boolean primitive type. + return booleanType; + } - if (isGenerator) { - return createGeneratorReturnType( - yieldType || neverType, - returnType || fallbackReturnType, - nextType || getContextualIterationType(IterationTypeKind.Next, func) || unknownType, - isAsync); + function checkObjectLiteralAssignment(node: ObjectLiteralExpression, sourceType: Type, rightIsThis?: boolean): Type { + const properties = node.properties; + if (strictNullChecks && properties.length === 0) { + return checkNonNullType(sourceType, node); } - 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; + for (let i = 0; i < properties.length; i++) { + checkObjectLiteralDestructuringPropertyAssignment(node, sourceType, i, properties, rightIsThis); } + return sourceType; } - 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]); + /** 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, /*writing*/ true, objectLiteralType, prop); } - - // 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; + const elementType = getIndexedAccessType(objectLiteralType, exprType, AccessFlags.ExpressionPosition, name); + const type = getFlowTypeOfDestructuring(property, elementType); + return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type); } - - 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 as Block, 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 if (property.kind === SyntaxKind.SpreadAssignment) { + if (propertyIndex < properties.length - 1) { + error(property, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); } else { - nextType = getContextualType(yieldExpression, /*contextFlags*/ undefined); + 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); } - 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); - } - - // Return the combined not-equal type facts for all cases except those between the start and end indices. - function getNotEqualFactsFromTypeofSwitch(start: number, end: number, witnesses: (string | undefined)[]): TypeFacts { - let facts: TypeFacts = TypeFacts.None; - for (let i = 0; i < witnesses.length; i++) { - const witness = i < start || i >= end ? witnesses[i] : undefined; - facts |= witness !== undefined ? typeofNEFacts.get(witness) || TypeFacts.TypeofNEHostObject : 0; } - return facts; + else { + error(property, Diagnostics.Property_assignment_expected); + } } - function isExhaustiveSwitchStatement(node: SwitchStatement): boolean { - const links = getNodeLinks(node); - if (links.isExhaustive === undefined) { - links.isExhaustive = 0; // Indicate resolution is in process - const exhaustive = computeExhaustiveSwitchStatement(node); - if (links.isExhaustive === 0) { - links.isExhaustive = exhaustive; + 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); } - else if (links.isExhaustive === 0) { - links.isExhaustive = false; // Resolve circularity to false - } - return links.isExhaustive; + return sourceType; } - function computeExhaustiveSwitchStatement(node: SwitchStatement): boolean { - if (node.expression.kind === SyntaxKind.TypeOfExpression) { - const witnesses = getSwitchClauseTypeOfWitnesses(node); - if (!witnesses) { - return false; + 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 = getNumberLiteralType(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, accessFlags, createSyntheticExpression(element, indexType)) || 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); } - const operandConstraint = getBaseConstraintOrType(checkExpressionCached((node.expression as TypeOfExpression).expression)); - // Get the not-equal flags for all handled cases. - const notEqualFacts = getNotEqualFactsFromTypeofSwitch(0, 0, witnesses); - if (operandConstraint.flags & TypeFlags.AnyOrUnknown) { - // We special case the top types to be exhaustive when all cases are handled. - return (TypeFacts.AllTypeofNE & notEqualFacts) === TypeFacts.AllTypeofNE; + if (elementIndex < elements.length - 1) { + error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); + } + else { + const restExpression = (element as SpreadElement).expression; + if (restExpression.kind === SyntaxKind.BinaryExpression && (restExpression as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { + error((restExpression as BinaryExpression).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 as TupleTypeReference, elementIndex)) : + createArrayType(elementType); + return checkDestructuringAssignment(restExpression, type, checkMode); + } } - // A missing not-equal flag indicates that the type wasn't handled by some case. - return !someType(operandConstraint, t => (getTypeFacts(t) & notEqualFacts) === notEqualFacts); - } - const type = checkExpressionCached(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); + return undefined; } - /** 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 as Block, 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 = unwrapAwaitedType(checkAwaitedType(type, /*withAlias*/ false, 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; + function checkDestructuringAssignment(exprOrAssignment: Expression | ShorthandPropertyAssignment, sourceType: Type, checkMode?: CheckMode, rightIsThis?: boolean): Type { + let target: Expression; + if (exprOrAssignment.kind === SyntaxKind.ShorthandPropertyAssignment) { + const prop = exprOrAssignment as ShorthandPropertyAssignment; + 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 && + !(getTypeFacts(checkExpression(prop.objectAssignmentInitializer)) & TypeFacts.IsUndefined)) { + sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); } - pushIfUnique(aggregatedTypes, type); + checkBinaryLikeExpression(prop.name, prop.equalsToken!, prop.objectAssignmentInitializer, checkMode); } - else { - hasReturnWithNoExpression = true; + target = (exprOrAssignment as ShorthandPropertyAssignment).name; + } + else { + target = exprOrAssignment; + } + + if (target.kind === SyntaxKind.BinaryExpression && (target as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { + checkBinaryExpression(target as BinaryExpression, checkMode); + target = (target as BinaryExpression).left; + // A default value is specified, so remove undefined from the final type. + if (strictNullChecks) { + sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); } - }); - 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); + if (target.kind === SyntaxKind.ObjectLiteralExpression) { + return checkObjectLiteralAssignment(target as ObjectLiteralExpression, sourceType, rightIsThis); } - return aggregatedTypes; + if (target.kind === SyntaxKind.ArrayLiteralExpression) { + return checkArrayLiteralAssignment(target as ArrayLiteralExpression, sourceType, checkMode); + } + return checkReferenceAssignment(target, sourceType, checkMode); } - 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; + + 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; } /** - * 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 + * 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 checkAllCodePathsInNonVoidFunctionReturnOrThrow(func: FunctionLikeDeclaration | MethodSignature, returnType: Type | undefined) { - addLazyDiagnostic(checkAllCodePathsInNonVoidFunctionReturnOrThrowDiagnostics); - return; - - function checkAllCodePathsInNonVoidFunctionReturnOrThrowDiagnostics(): void { - const functionFlags = getFunctionFlags(func); - const type = returnType && unwrapReturnType(returnType, functionFlags); - - // Functions with an explicitly specified return type that includes `void` or is exactly `any` or `undefined` don't - // need any return statements. - if (type && (maybeTypeOfKind(type, TypeFlags.Void) || type.flags & (TypeFlags.Any | TypeFlags.Undefined))) { - 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; - } + 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; - const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn; - const errorNode = getEffectiveReturnTypeNode(func) || func; + case SyntaxKind.ConditionalExpression: + return isSideEffectFree((node as ConditionalExpression).whenTrue) && + isSideEffectFree((node as ConditionalExpression).whenFalse); - if (type && type.flags & TypeFlags.Never) { - error(errorNode, 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. - error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_undefined_void_nor_any_must_return_a_value); - } - else if (type && strictNullChecks && !isTypeAssignableTo(undefinedType, type)) { - error(errorNode, 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 (isUnwrappedReturnTypeUndefinedVoidOrAny(func, inferredReturnType)) { - return; - } + case SyntaxKind.BinaryExpression: + if (isAssignmentOperator((node as BinaryExpression).operatorToken.kind)) { + return false; } - error(errorNode, 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); - - if (isFunctionExpression(node)) { - checkCollisionsForDeclarationName(node, node.name); - } + return isSideEffectFree((node as BinaryExpression).left) && + isSideEffectFree((node as BinaryExpression).right); - // 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(/*declaration*/ undefined, /*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.IsNonInferrable); - const returnOnlyType = createAnonymousType(node.symbol, emptySymbols, [returnOnlySignature], emptyArray, emptyArray); - returnOnlyType.objectFlags |= ObjectFlags.NonInferrableType; - return links.contextFreeType = returnOnlyType; + 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 anyFunctionType; - } + return false; - // Grammar checking - const hasGrammarError = checkGrammarFunctionLikeDeclaration(node); - if (!hasGrammarError && node.kind === SyntaxKind.FunctionExpression) { - checkGrammarForGenerator(node); + // 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; } + } - contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node, checkMode); - - return getTypeOfSymbol(getSymbolOfDeclaration(node)); + function isTypeEqualityComparableTo(source: Type, target: Type) { + return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target); } - 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(getSymbolOfDeclaration(node)), SignatureKind.Call)); - if (!signature) { - return; - } - if (isContextSensitive(node)) { - if (contextualSignature) { - const inferenceContext = getInferenceContext(node); - let instantiatedContextualSignature: Signature | undefined; - if (checkMode && checkMode & CheckMode.Inferential) { - inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); - const restType = getEffectiveRestType(contextualSignature); - if (restType && restType.flags & TypeFlags.TypeParameter) { - instantiatedContextualSignature = instantiateSignature(contextualSignature, inferenceContext!.nonFixingMapper); - } - } - 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); - } - } - else if (contextualSignature && !node.typeParameters && contextualSignature.parameters.length > node.parameters.length) { - const inferenceContext = getInferenceContext(node); - if (checkMode && checkMode & CheckMode.Inferential) { - inferFromAnnotatedParameters(signature, contextualSignature, inferenceContext!); - } - } - if (contextualSignature && !getReturnTypeFromAnnotation(node) && !signature.resolvedReturnType) { - const returnType = getReturnTypeFromBody(node, checkMode); - if (!signature.resolvedReturnType) { - signature.resolvedReturnType = returnType; - } - } - checkSignatureDeclaration(node); - } + function createCheckBinaryExpression() { + interface WorkArea { + readonly checkMode: CheckMode | undefined; + skip: boolean; + stackIndex: number; + /** + * Holds the types from the left-side of an expression from [0..stackIndex]. + * Holds the type of the result at stackIndex+1. This allows us to reuse existing stack entries + * and avoid storing an extra property on the object (i.e., `lastResult`). + */ + typeStack: (Type | undefined)[]; } - } - function checkFunctionExpressionOrObjectLiteralMethodDeferred(node: ArrowFunction | FunctionExpression | MethodDeclaration) { - Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); + const trampoline = createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, foldState); - const functionFlags = getFunctionFlags(node); - const returnType = getReturnTypeFromAnnotation(node); - checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); + return (node: BinaryExpression, checkMode: CheckMode | undefined) => { + const result = trampoline(node, checkMode); + Debug.assertIsDefined(result); + return result; + }; - 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)); + function onEnter(node: BinaryExpression, state: WorkArea | undefined, checkMode: CheckMode | undefined) { + if (state) { + state.stackIndex++; + state.skip = false; + setLeftType(state, /*type*/ undefined); + setLastResult(state, /*type*/ undefined); + } + else { + state = { + checkMode, + skip: false, + stackIndex: 0, + typeStack: [undefined, undefined], + }; } - if (node.body.kind === SyntaxKind.Block) { - checkSourceElement(node.body); + if (isInJSFile(node) && getAssignedExpandoInitializer(node)) { + state.skip = true; + setLastResult(state, checkExpression(node.right, checkMode)); + return state; } - 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, /*withAlias*/ false, 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); - } - } + + checkGrammarNullishCoalesceWithLogicalExpression(node); + + const operator = node.operatorToken.kind; + if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) { + state.skip = true; + setLastResult(state, checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword)); + return state; } - } - } - 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 state; } - 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; - } + function onLeft(left: Expression, state: WorkArea, _node: BinaryExpression) { + if (!state.skip) { + return maybeCheckExpression(state, left); } - 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; + function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, node: BinaryExpression) { + if (!state.skip) { + const leftType = getLastResult(state); + Debug.assertIsDefined(leftType); + setLeftType(state, leftType); + setLastResult(state, /*type*/ undefined); + const operator = operatorToken.kind; + if (isLogicalOrCoalescingBinaryOperator(operator)) { + let parent = node.parent; + while (parent.kind === SyntaxKind.ParenthesizedExpression || isLogicalOrCoalescingBinaryExpression(parent)) { + parent = parent.parent; + } + if (operator === SyntaxKind.AmpersandAmpersandToken || isIfStatement(parent)) { + checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined); + } + checkTruthinessOfType(leftType, node.left); } } - 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; - } + + function onRight(right: Expression, state: WorkArea, _node: BinaryExpression) { + if (!state.skip) { + return maybeCheckExpression(state, right); } } - 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; + function onExit(node: BinaryExpression, state: WorkArea): Type | undefined { + let result: Type | undefined; + if (state.skip) { + result = getLastResult(state); + } + else { + const leftType = getLeftType(state); + Debug.assertIsDefined(leftType); + + const rightType = getLastResult(state); + Debug.assertIsDefined(rightType); + + result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node); + } + + state.skip = false; + setLeftType(state, /*type*/ undefined); + setLastResult(state, /*type*/ undefined); + state.stackIndex--; + return result; } - if (node.flags & NodeFlags.OptionalChain) { - error(expr, invalidOptionalChainMessage); - return false; + + function foldState(state: WorkArea, result: Type | undefined, _side: "left" | "right") { + setLastResult(state, result); + return state; } - 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; + function maybeCheckExpression(state: WorkArea, node: Expression): BinaryExpression | undefined { + if (isBinaryExpression(node)) { + return node; + } + setLastResult(state, checkExpression(node, state.checkMode)); } - if (isPropertyAccessExpression(expr) && isPrivateIdentifier(expr.name)) { - error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_private_identifier); + + function getLeftType(state: WorkArea) { + return state.typeStack[state.stackIndex]; } - 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, symbol); + + function setLeftType(state: WorkArea, type: Type | undefined) { + state.typeStack[state.stackIndex] = type; } - return booleanType; - } - function checkDeleteExpressionMustBeOptional(expr: AccessExpression, symbol: Symbol) { - const type = getTypeOfSymbol(symbol); - if (strictNullChecks && - !(type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Never)) && - !(exactOptionalPropertyTypes ? symbol.flags & SymbolFlags.Optional : getTypeFacts(type) & TypeFacts.IsUndefined)) { - error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_optional); + function getLastResult(state: WorkArea) { + return state.typeStack[state.stackIndex + 1]; + } + + function setLastResult(state: WorkArea, type: Type | undefined) { + // To reduce overhead, reuse the next stack entry to store the + // last result. This avoids the overhead of an additional property + // on `WorkArea` and reuses empty stack entries as we walk back up + // the stack. + state.typeStack[state.stackIndex + 1] = type; } } - function checkTypeOfExpression(node: TypeOfExpression): Type { - checkExpression(node.expression); - return typeofType; + 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)); + } + } } - function checkVoidExpression(node: VoidExpression): Type { - checkExpression(node.expression); - return undefinedWideningType; + // 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: BinaryOperatorToken, 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 (isLogicalOrCoalescingBinaryOperator(operator)) { + leftType = checkTruthinessExpression(left, checkMode); + } + else { + leftType = checkExpression(left, checkMode); + } + + const rightType = checkExpression(right, checkMode); + return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, errorNode); } - function checkAwaitExpressionGrammar(node: AwaitExpression): void { - // Grammar checking - const container = getContainingFunctionOrClassStaticBlock(node); - if (container && isClassStaticBlockDeclaration(container)) { - error(node, Diagnostics.Await_expression_cannot_be_used_inside_a_class_static_block); + function checkBinaryLikeExpressionWorker( + left: Expression, + operatorToken: BinaryOperatorToken, + right: Expression, + leftType: Type, + rightType: Type, + errorNode?: Node + ): Type { + const operator = operatorToken.kind; + + const links = getNodeLinks(operatorToken); + + if (links.isTsPlusOperatorToken) { + return links.resolvedSignature ? getReturnTypeOfSignature(links.resolvedSignature) : errorType; } - else if (!(node.flags & NodeFlags.AwaitContext)) { - if (isInTopLevelContext(node)) { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - let span: TextSpan | undefined; - if (!isEffectiveExternalModule(sourceFile, compilerOptions)) { - 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); + + 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: PunctuationSyntaxKind | 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; } - switch (moduleKind) { - case ModuleKind.Node16: - case ModuleKind.NodeNext: - if (sourceFile.impliedNodeFormat === ModuleKind.CommonJS) { - span ??= getSpanOfTokenAtPosition(sourceFile, node.pos); - diagnostics.add( - createFileDiagnostic(sourceFile, span.start, span.length, Diagnostics.The_current_file_is_a_CommonJS_module_and_cannot_use_await_at_the_top_level) - ); - break; - } - // fallthrough - case ModuleKind.ES2022: - case ModuleKind.ESNext: - case ModuleKind.System: - if (languageVersion >= ScriptTarget.ES2017) { + // 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; - } - // fallthrough - default: - span ??= getSpanOfTokenAtPosition(sourceFile, node.pos); - diagnostics.add( - createFileDiagnostic(sourceFile, span.start, span.length, - Diagnostics.Top_level_await_expressions_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher - ) - ); - 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; } - } - } - 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); - if (container && container.kind !== SyntaxKind.Constructor && (getFunctionFlags(container) & FunctionFlags.Async) === 0) { - const relatedInfo = createDiagnosticForNode(container, Diagnostics.Did_you_mean_to_mark_this_function_as_async); - addRelatedInfo(diagnostic, relatedInfo); + // Exactly one of leftType/rightType is assignable to bigint + else { + reportOperatorError(bothAreBigIntLike); + resultType = errorType; } - diagnostics.add(diagnostic); + if (leftOk && rightOk) { + checkAssignmentOperator(resultType); + } + return resultType; + } + case SyntaxKind.PlusToken: + case SyntaxKind.PlusEqualsToken: + if (leftType === silentNeverType || rightType === silentNeverType) { + return silentNeverType; } - } - } - if (isInParameterInitializerBeforeContainingFunction(node)) { - error(node, Diagnostics.await_expressions_cannot_be_used_in_a_parameter_initializer); - } - } + if (!isTypeAssignableToKind(leftType, TypeFlags.StringLike) && !isTypeAssignableToKind(rightType, TypeFlags.StringLike)) { + leftType = checkNonNullType(leftType, left); + rightType = checkNonNullType(rightType, right); + } - function checkAwaitExpression(node: AwaitExpression): Type { - addLazyDiagnostic(() => checkAwaitExpressionGrammar(node)); + 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 = isErrorType(leftType) || isErrorType(rightType) ? errorType : anyType; + } - const operandType = checkExpression(node.expression); - const awaitedType = checkAwaitedType(operandType, /*withAlias*/ true, node, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); - if (awaitedType === operandType && !isErrorType(awaitedType) && !(operandType.flags & TypeFlags.AnyOrUnknown)) { - addErrorOrSuggestion(/*isError*/ false, createDiagnosticForNode(node, Diagnostics.await_has_no_effect_on_the_type_of_this_expression)); - } - return awaitedType; - } + // Symbols are not allowed at all in arithmetic expressions + if (resultType && !checkForDisallowedESSymbolOperand(operator)) { + return resultType; + } - 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(getNumberLiteralType(-(node.operand as NumericLiteral).text)); - case SyntaxKind.PlusToken: - return getFreshTypeOfLiteralType(getNumberLiteralType(+(node.operand as NumericLiteral).text)); + 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; } - break; - case SyntaxKind.BigIntLiteral: - if (node.operator === SyntaxKind.MinusToken) { - return getFreshTypeOfLiteralType(getBigIntLiteralType({ - negative: true, - base10Value: parsePseudoBigInt((node.operand as BigIntLiteral).text) - })); + + if (operator === SyntaxKind.PlusEqualsToken) { + checkAssignmentOperator(resultType); } - } - switch (node.operator) { - case SyntaxKind.PlusToken: - case SyntaxKind.MinusToken: - case SyntaxKind.TildeToken: - checkNonNullType(operandType, node.operand); - if (maybeTypeOfKindConsideringBaseConstraint(operandType, TypeFlags.ESSymbolLike)) { - error(node.operand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(node.operator)); + return resultType; + case SyntaxKind.LessThanToken: + case SyntaxKind.GreaterThanToken: + case SyntaxKind.LessThanEqualsToken: + case SyntaxKind.GreaterThanEqualsToken: + if (checkForDisallowedESSymbolOperand(operator)) { + leftType = getBaseTypeOfLiteralTypeForComparison(checkNonNullType(leftType, left)); + rightType = getBaseTypeOfLiteralTypeForComparison(checkNonNullType(rightType, right)); + reportOperatorErrorUnless((left, right) => { + if (isTypeAny(left) || isTypeAny(right)) { + return true; + } + const leftAssignableToNumber = isTypeAssignableTo(left, numberOrBigIntType); + const rightAssignableToNumber = isTypeAssignableTo(right, numberOrBigIntType); + return leftAssignableToNumber && rightAssignableToNumber || + !leftAssignableToNumber && !rightAssignableToNumber && areTypesComparable(left, right); + }); } - if (node.operator === SyntaxKind.PlusToken) { - if (maybeTypeOfKindConsideringBaseConstraint(operandType, TypeFlags.BigIntLike)) { - error(node.operand, Diagnostics.Operator_0_cannot_be_applied_to_type_1, tokenToString(node.operator), typeToString(getBaseTypeOfLiteralType(operandType))); + return booleanType; + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + if (isLiteralExpressionOfObject(left) || isLiteralExpressionOfObject(right)) { + const eqType = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken; + error(errorNode, Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value, eqType ? "false" : "true"); + } + checkNaNEquality(errorNode, operator, left, right); + 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([getNonNullableType(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 numberType; + return leftType; } - return getUnaryResultType(operandType); - case SyntaxKind.ExclamationToken: - checkTruthinessOfType(operandType, 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); + else { + checkAssignmentOperator(rightType); + return rightType; } - 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; - } + case SyntaxKind.CommaToken: + if (!compilerOptions.allowUnreachableCode && isSideEffectFree(left) && !isIndirectCall(left.parent as BinaryExpression)) { + 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; - function maybeTypeOfKindConsideringBaseConstraint(type: Type, kind: TypeFlags): boolean { - if (maybeTypeOfKind(type, kind)) { - return true; + default: + return Debug.fail(); } - const baseConstraint = getBaseConstraintOrType(type); - return !!baseConstraint && maybeTypeOfKind(baseConstraint, kind); - } - - // 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; + function bothAreBigIntLike(left: Type, right: Type): boolean { + return isTypeAssignableToKind(left, TypeFlags.BigIntLike) && isTypeAssignableToKind(right, TypeFlags.BigIntLike); } - if (type.flags & TypeFlags.UnionOrIntersection) { - const types = (type as UnionOrIntersectionType).types; - for (const t of types) { - if (maybeTypeOfKind(t, kind)) { - return true; + + 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, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ false); + if (symbol?.declarations && symbol.declarations.some(isJSDocTypedefTag)) { + addDuplicateDeclarationErrorsForSymbols(symbol, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name), prop); + addDuplicateDeclarationErrorsForSymbols(prop, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name), symbol); + } + } } } } - 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 true for "indirect calls", (i.e. `(0, x.f)(...)` or `(0, eval)(...)`), which prevents passing `this`. + function isIndirectCall(node: BinaryExpression): boolean { + return node.parent.kind === SyntaxKind.ParenthesizedExpression && + isNumericLiteral(node.left) && + node.left.text === "0" && + (isCallExpression(node.parent.parent) && node.parent.parent.expression === node.parent || node.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) && + // special-case for "eval" because it's the only non-access case where an indirect call actually affects behavior. + (isAccessExpression(node.right) || isIdentifier(node.right) && node.right.escapedText === "eval"); } - 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); - } + // Return true if there was no error, false if there was an error. + function checkForDisallowedESSymbolOperand(operator: PunctuationSyntaxKind): boolean { + const offendingSymbolOperand = + maybeTypeOfKindConsideringBaseConstraint(leftType, TypeFlags.ESSymbolLike) ? left : + maybeTypeOfKindConsideringBaseConstraint(rightType, TypeFlags.ESSymbolLike) ? right : + undefined; - function isConstEnumSymbol(symbol: Symbol): boolean { - return (symbol.flags & SymbolFlags.ConstEnum) !== 0; - } + if (offendingSymbolOperand) { + error(offendingSymbolOperand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(operator)); + return false; + } - 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 true; } - return booleanType; - } - - function hasEmptyObjectIntersection(type: Type): boolean { - return someType(type, t => t === unknownEmptyObjectType || !!(t.flags & TypeFlags.Intersection) && isEmptyAnonymousObjectType(getBaseConstraintOrType(t))); - } - function checkInExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { - if (leftType === silentNeverType || rightType === silentNeverType) { - return silentNeverType; - } - if (isPrivateIdentifier(left)) { - if (languageVersion < ScriptTarget.ESNext) { - checkExternalEmitHelpers(left, ExternalEmitHelpers.ClassPrivateFieldIn); - } - // Unlike in 'checkPrivateIdentifierExpression' we now have access to the RHS type - // which provides us with the opportunity to emit more detailed errors - if (!getNodeLinks(left).resolvedSymbol && getContainingClass(left)) { - const isUncheckedJS = isUncheckedJSSuggestion(left, rightType.symbol, /*excludeClasses*/ true); - reportNonexistentProperty(left, rightType, isUncheckedJS); + function getSuggestedBooleanOperator(operator: SyntaxKind): PunctuationSyntaxKind | 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; } } - else { - // The type of the lef operand must be assignable to string, number, or symbol. - checkTypeAssignableTo(checkNonNullType(leftType, left), stringNumberSymbolType, left); - } - // The type of the right operand must be assignable to 'object'. - if (checkTypeAssignableTo(checkNonNullType(rightType, right), nonPrimitiveType, right)) { - // The {} type is assignable to the object type, yet {} might represent a primitive type. Here we - // detect and error on {} that results from narrowing the unknown type, as well as intersections - // that include {} (we know that the other types in such intersections are assignable to object - // since we already checked for that). - if (hasEmptyObjectIntersection(rightType)) { - error(right, Diagnostics.Type_0_may_represent_a_primitive_value_which_is_not_permitted_as_the_right_operand_of_the_in_operator, typeToString(rightType)); + + function checkAssignmentOperator(valueType: Type): void { + if (isAssignmentOperator(operator)) { + addLazyDiagnostic(checkAssignmentOperatorWorker); } - } - // The result is always of the Boolean primitive type. - 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; - } + function checkAssignmentOperatorWorker() { + let assigneeType = leftType; - /** 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, /*writing*/ true, objectLiteralType, prop); - } - } - const elementType = getIndexedAccessType(objectLiteralType, exprType, AccessFlags.ExpressionPosition, name); - 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); + // getters can be a subtype of setters, so to check for assignability we use the setter's type instead + if (isCompoundAssignment(operatorToken.kind) && left.kind === SyntaxKind.PropertyAccessExpression) { + assigneeType = checkPropertyAccessExpression(left as PropertyAccessExpression, /*checkMode*/ undefined, /*writeOnly*/ true); } - const nonRestNames: PropertyName[] = []; - if (allProperties) { - for (const otherProperty of allProperties) { - if (!isSpreadAssignment(otherProperty)) { - nonRestNames.push(otherProperty.name); + + // 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) + ) { + + let headMessage: DiagnosticMessage | undefined; + if (exactOptionalPropertyTypes && isPropertyAccessExpression(left) && maybeTypeOfKind(valueType, TypeFlags.Undefined)) { + const target = getTypeOfPropertyOfType(getTypeOfExpression(left.expression), left.name.escapedText); + if (isExactOptionalPropertyMismatch(valueType, target)) { + headMessage = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target; } } + // to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported + checkTypeAssignableToAndOptionallyElaborate(valueType, assigneeType, left, right, headMessage); } - 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); + 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; + } } - // 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); + + /** + * Returns true if an error is reported + */ + function reportOperatorErrorUnless(typesAreCompatible: (left: Type, right: Type) => boolean): boolean { + if (!typesAreCompatible(leftType, rightType)) { + reportOperatorError(typesAreCompatible); + return true; } - checkArrayLiteralDestructuringElementAssignment(node, sourceType, i, type, checkMode); + return false; } - 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 = getNumberLiteralType(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, accessFlags, createSyntheticExpression(element, indexType)) || 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); + function reportOperatorError(isRelated?: (left: Type, right: Type) => boolean) { + let wouldWorkWithAwait = false; + const errNode = errorNode || operatorToken; + if (isRelated) { + const awaitedLeftType = getAwaitedTypeNoAlias(leftType); + const awaitedRightType = getAwaitedTypeNoAlias(rightType); + wouldWorkWithAwait = !(awaitedLeftType === leftType && awaitedRightType === rightType) + && !!(awaitedLeftType && awaitedRightType) + && isRelated(awaitedLeftType, awaitedRightType); } - if (elementIndex < elements.length - 1) { - error(element, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); + + let effectiveLeft = leftType; + let effectiveRight = rightType; + if (!wouldWorkWithAwait && isRelated) { + [effectiveLeft, effectiveRight] = getBaseTypesIfUnrelated(leftType, rightType, isRelated); } - else { - const restExpression = (element as SpreadElement).expression; - if (restExpression.kind === SyntaxKind.BinaryExpression && (restExpression as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { - error((restExpression as BinaryExpression).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 as TupleTypeReference, elementIndex)) : - createArrayType(elementType); - return checkDestructuringAssignment(restExpression, type, checkMode); - } + 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, + ); } } - 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 as ShorthandPropertyAssignment; - 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 && - !(getTypeFacts(checkExpression(prop.objectAssignmentInitializer)) & TypeFacts.IsUndefined)) { - sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); - } - checkBinaryLikeExpression(prop.name, prop.equalsToken!, prop.objectAssignmentInitializer, checkMode); + function tryGiveBetterPrimaryError(errNode: Node, maybeMissingAwait: boolean, leftStr: string, rightStr: string) { + switch (operatorToken.kind) { + case SyntaxKind.EqualsEqualsEqualsToken: + case SyntaxKind.EqualsEqualsToken: + case SyntaxKind.ExclamationEqualsEqualsToken: + case SyntaxKind.ExclamationEqualsToken: + return errorAndMaybeSuggestAwait( + errNode, + maybeMissingAwait, + Diagnostics.This_comparison_appears_to_be_unintentional_because_the_types_0_and_1_have_no_overlap, + leftStr, rightStr); + default: + return undefined; } - target = (exprOrAssignment as ShorthandPropertyAssignment).name; - } - else { - target = exprOrAssignment; } - if (target.kind === SyntaxKind.BinaryExpression && (target as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { - checkBinaryExpression(target as BinaryExpression, checkMode); - target = (target as BinaryExpression).left; - // A default value is specified, so remove undefined from the final type. - if (strictNullChecks) { - sourceType = getTypeWithFacts(sourceType, TypeFacts.NEUndefined); + function checkNaNEquality(errorNode: Node | undefined, operator: SyntaxKind, left: Expression, right: Expression) { + const isLeftNaN = isGlobalNaN(skipParentheses(left)); + const isRightNaN = isGlobalNaN(skipParentheses(right)); + if (isLeftNaN || isRightNaN) { + const err = error(errorNode, Diagnostics.This_condition_will_always_return_0, + tokenToString(operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.EqualsEqualsToken ? SyntaxKind.FalseKeyword : SyntaxKind.TrueKeyword)); + if (isLeftNaN && isRightNaN) return; + const operatorString = operator === SyntaxKind.ExclamationEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken ? tokenToString(SyntaxKind.ExclamationToken) : ""; + const location = isLeftNaN ? right : left; + const expression = skipParentheses(location); + addRelatedInfo(err, createDiagnosticForNode(location, Diagnostics.Did_you_mean_0, + `${operatorString}Number.isNaN(${isEntityNameExpression(expression) ? entityNameToString(expression) : "..."})`)); } } - if (target.kind === SyntaxKind.ObjectLiteralExpression) { - return checkObjectLiteralAssignment(target as ObjectLiteralExpression, sourceType, rightIsThis); - } - if (target.kind === SyntaxKind.ArrayLiteralExpression) { - return checkArrayLiteralAssignment(target as ArrayLiteralExpression, 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 isGlobalNaN(expr: Expression): boolean { + if (isIdentifier(expr) && expr.escapedText === "NaN") { + const globalNaNSymbol = getGlobalNaNSymbol(); + return !!globalNaNSymbol && globalNaNSymbol === getResolvedSymbol(expr); + } + return false; } } - function isTypeEqualityComparableTo(source: Type, target: Type) { - return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target); - } - - function createCheckBinaryExpression() { - interface WorkArea { - readonly checkMode: CheckMode | undefined; - skip: boolean; - stackIndex: number; - /** - * Holds the types from the left-side of an expression from [0..stackIndex]. - * Holds the type of the result at stackIndex+1. This allows us to reuse existing stack entries - * and avoid storing an extra property on the object (i.e., `lastResult`). - */ - typeStack: (Type | undefined)[]; - } - - const trampoline = createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, foldState); - - return (node: BinaryExpression, checkMode: CheckMode | undefined) => { - const result = trampoline(node, checkMode); - Debug.assertIsDefined(result); - return result; - }; - - function onEnter(node: BinaryExpression, state: WorkArea | undefined, checkMode: CheckMode | undefined) { - if (state) { - state.stackIndex++; - state.skip = false; - setLeftType(state, /*type*/ undefined); - setLastResult(state, /*type*/ undefined); - } - else { - state = { - checkMode, - skip: false, - stackIndex: 0, - typeStack: [undefined, 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 ]; + } - if (isInJSFile(node) && getAssignedExpandoInitializer(node)) { - state.skip = true; - setLastResult(state, checkExpression(node.right, checkMode)); - return state; - } + function checkYieldExpression(node: YieldExpression): Type { + addLazyDiagnostic(checkYieldExpressionGrammar); - checkGrammarNullishCoalesceWithLogicalExpression(node); + const func = getContainingFunction(node); + if (!func) return anyType; + const functionFlags = getFunctionFlags(func); - const operator = node.operatorToken.kind; - if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) { - state.skip = true; - setLastResult(state, checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword)); - return state; + 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); } - return state; + // Generator functions prior to ES2015 require the __values helper + if (!isAsync && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Values); + } } - function onLeft(left: Expression, state: WorkArea, _node: BinaryExpression) { - if (!state.skip) { - return maybeCheckExpression(state, left); - } + // 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); } - function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, node: BinaryExpression) { - if (!state.skip) { - const leftType = getLastResult(state); - Debug.assertIsDefined(leftType); - setLeftType(state, leftType); - setLastResult(state, /*type*/ undefined); - const operator = operatorToken.kind; - if (isLogicalOrCoalescingBinaryOperator(operator)) { - let parent = node.parent; - while (parent.kind === SyntaxKind.ParenthesizedExpression || isLogicalOrCoalescingBinaryExpression(parent)) { - parent = parent.parent; - } - if (operator === SyntaxKind.AmpersandAmpersandToken || isIfStatement(parent)) { - checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined); + 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; + } + let type = getContextualIterationType(IterationTypeKind.Next, func); + if (!type) { + type = anyType; + addLazyDiagnostic(() => { + if (noImplicitAny && !expressionResultIsUnused(node)) { + const contextualType = getContextualType(node, /*contextFlags*/ undefined); + if (!contextualType || isTypeAny(contextualType)) { + error(node, Diagnostics.yield_expression_implicitly_results_in_an_any_type_because_its_containing_generator_lacks_a_return_type_annotation); } - checkTruthinessOfType(leftType, node.left); } - } + }); } + return type; - function onRight(right: Expression, state: WorkArea, _node: BinaryExpression) { - if (!state.skip) { - return maybeCheckExpression(state, right); + function checkYieldExpressionGrammar() { + if (!(node.flags & NodeFlags.YieldContext)) { + grammarErrorOnFirstToken(node, Diagnostics.A_yield_expression_is_only_allowed_in_a_generator_body); } - } - function onExit(node: BinaryExpression, state: WorkArea): Type | undefined { - let result: Type | undefined; - if (state.skip) { - result = getLastResult(state); + if (isInParameterInitializerBeforeContainingFunction(node)) { + error(node, Diagnostics.yield_expressions_cannot_be_used_in_a_parameter_initializer); } - else { - const leftType = getLeftType(state); - Debug.assertIsDefined(leftType); + } + } - const rightType = getLastResult(state); - Debug.assertIsDefined(rightType); + function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type { + const type = checkTruthinessExpression(node.condition); + checkTestingKnownTruthyCallableOrAwaitableType(node.condition, type, node.whenTrue); + const type1 = checkExpression(node.whenTrue, checkMode); + const type2 = checkExpression(node.whenFalse, checkMode); + return getUnionType([type1, type2], UnionReduction.Subtype); + } - result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node); - } + function isTemplateLiteralContext(node: Node): boolean { + const parent = node.parent; + return isParenthesizedExpression(parent) && isTemplateLiteralContext(parent) || + isElementAccessExpression(parent) && parent.argumentExpression === node; + } - state.skip = false; - setLeftType(state, /*type*/ undefined); - setLastResult(state, /*type*/ undefined); - state.stackIndex--; - return result; + function checkTemplateExpression(node: TemplateExpression): Type { + const texts = [node.head.text]; + const types = []; + for (const span of node.templateSpans) { + const type = checkExpression(span.expression); + if (maybeTypeOfKindConsideringBaseConstraint(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) || isTemplateLiteralContext(node) || someType(getContextualType(node, /*contextFlags*/ undefined) || unknownType, isTemplateLiteralContextualType) ? getTemplateLiteralType(texts, types) : stringType; + } - function foldState(state: WorkArea, result: Type | undefined, _side: "left" | "right") { - setLastResult(state, result); - return state; - } + function isTemplateLiteralContextualType(type: Type): boolean { + return !!(type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral) || + type.flags & TypeFlags.InstantiableNonPrimitive && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.StringLike)); + } - function maybeCheckExpression(state: WorkArea, node: Expression): BinaryExpression | undefined { - if (isBinaryExpression(node)) { - return node; - } - setLastResult(state, checkExpression(node, state.checkMode)); + function getContextNode(node: Expression): Expression { + if (isJsxAttributes(node) && !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 getLeftType(state: WorkArea) { - return state.typeStack[state.stackIndex]; + function checkExpressionWithContextualType(node: Expression, contextualType: Type, inferenceContext: InferenceContext | undefined, checkMode: CheckMode): Type { + const contextNode = getContextNode(node); + pushContextualType(contextNode, contextualType, /*isCache*/ false); + pushInferenceContext(contextNode, inferenceContext); + const type = checkExpression(node, checkMode | CheckMode.Contextual | (inferenceContext ? CheckMode.Inferential : 0)); + // In CheckMode.Inferential we collect intra-expression inference sites to process before fixing any type + // parameters. This information is no longer needed after the call to checkExpression. + if (inferenceContext && inferenceContext.intraExpressionInferenceSites) { + inferenceContext.intraExpressionInferenceSites = undefined; } + // 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, /*contextFlags*/ undefined)) ? + getRegularTypeOfLiteralType(type) : type; + popInferenceContext(); + popContextualType(); + return result; + } - function setLeftType(state: WorkArea, type: Type | undefined) { - state.typeStack[state.stackIndex] = type; + function checkExpressionCached(node: Expression | QualifiedName, checkMode?: CheckMode): Type { + if (checkMode) { + return checkExpression(node, checkMode); + } + const links = getNodeLinks(node); + if (!links.resolvedType) { + // 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 getLastResult(state: WorkArea) { - return state.typeStack[state.stackIndex + 1]; + function isTypeAssertion(node: Expression) { + node = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); + return node.kind === SyntaxKind.TypeAssertionExpression || + node.kind === SyntaxKind.AsExpression || + isJSDocTypeAssertion(node); + } + + function checkDeclarationInitializer( + declaration: HasExpressionInitializer, + checkMode: CheckMode, + contextualType?: Type | undefined + ) { + const initializer = getEffectiveInitializer(declaration)!; + if (isInJSFile(declaration)) { + const typeNode = tryGetJSDocSatisfiesTypeNode(declaration); + if (typeNode) { + return checkSatisfiesExpressionWorker(initializer, typeNode, checkMode); + } } + const type = getQuickTypeOfExpression(initializer) || + (contextualType ? + checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, checkMode || CheckMode.Normal) + : checkExpressionCached(initializer, checkMode)); + 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 setLastResult(state: WorkArea, type: Type | undefined) { - // To reduce overhead, reuse the next stack entry to store the - // last result. This avoids the overhead of an additional property - // on `WorkArea` and reuses empty stack entries as we walk back up - // the stack. - state.typeStack[state.stackIndex + 1] = 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 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)); + function widenTypeInferredFromInitializer(declaration: HasExpressionInitializer, type: Type) { + const widened = getCombinedNodeFlags(declaration) & NodeFlags.Const || isDeclarationReadonly(declaration) ? type : getWidenedLiteralType(type); + if (isInJSFile(declaration)) { + if (isEmptyLiteralType(widened)) { + reportImplicitAny(declaration, anyType); + return anyType; } - 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)); + else if (isEmptyArrayLiteralType(widened)) { + reportImplicitAny(declaration, anyArrayType); + return anyArrayType; } } + return widened; } - // 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: BinaryOperatorToken, 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 (isLogicalOrCoalescingBinaryOperator(operator)) { - leftType = checkTruthinessExpression(left, checkMode); - } - else { - leftType = checkExpression(left, checkMode); + function isLiteralOfContextualType(candidateType: Type, contextualType: Type | undefined): boolean { + if (contextualType) { + if (contextualType.flags & TypeFlags.UnionOrIntersection) { + const types = (contextualType as UnionType).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)); } - - const rightType = checkExpression(right, checkMode); - return checkBinaryLikeExpressionWorker(left, operatorToken, right, leftType, rightType, errorNode); + return false; } - function checkBinaryLikeExpressionWorker( - left: Expression, - operatorToken: BinaryOperatorToken, - right: Expression, - leftType: Type, - rightType: Type, - errorNode?: Node - ): Type { - const operator = operatorToken.kind; + function isConstContext(node: Expression): boolean { + const parent = node.parent; + return isAssertionExpression(parent) && isConstTypeReference(parent.type) || + isJSDocTypeAssertion(parent) && isConstTypeReference(getJSDocTypeAssertionType(parent)) || + isValidConstAssertionArgument(node) && isConstTypeVariable(getContextualType(node, ContextFlags.None)) || + (isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) || + (isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent) || isTemplateSpan(parent)) && isConstContext(parent.parent); + } - const links = getNodeLinks(operatorToken); + function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, forceTuple?: boolean): Type { + const type = checkExpression(node, checkMode, forceTuple); + return isConstContext(node) || isCommonJsExportedExpression(node) ? getRegularTypeOfLiteralType(type) : + isTypeAssertion(node) ? type : + getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(getContextualType(node, /*contextFlags*/ undefined), node, /*contextFlags*/ undefined)); + } - if (links.isTsPlusOperatorToken) { - return links.resolvedSignature ? getReturnTypeOfSignature(links.resolvedSignature) : errorType; + 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); } - 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; - } + return checkExpressionForMutableLocation(node.initializer, checkMode); + } - leftType = checkNonNullType(leftType, left); - rightType = checkNonNullType(rightType, right); + function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode): Type { + // Grammar checking + checkGrammarMethod(node); - let suggestedOperator: PunctuationSyntaxKind | 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); + // 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 as Expression, 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); } + } } - resultType = bigintType; - } - // Exactly one of leftType/rightType is assignable to bigint - else { - reportOperatorError(bothAreBigIntLike); - resultType = errorType; - } - if (leftOk && rightOk) { - checkAssignmentOperator(resultType); + return getOrCreateTypeFromSignature(instantiateSignatureInContextOf(signature, contextualSignature, context)); } - return resultType; - } - case SyntaxKind.PlusToken: - case SyntaxKind.PlusEqualsToken: - if (leftType === silentNeverType || rightType === silentNeverType) { - return silentNeverType; } + } + } + return type; + } - if (!isTypeAssignableToKind(leftType, TypeFlags.StringLike) && !isTypeAssignableToKind(rightType, TypeFlags.StringLike)) { - leftType = checkNonNullType(leftType, left); - rightType = checkNonNullType(rightType, right); - } + 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; + } + } - 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 = isErrorType(leftType) || isErrorType(rightType) ? errorType : anyType; - } + function hasInferenceCandidates(info: InferenceInfo) { + return !!(info.candidates || info.contraCandidates); + } - // Symbols are not allowed at all in arithmetic expressions - if (resultType && !checkForDisallowedESSymbolOperand(operator)) { - return resultType; - } + function hasInferenceCandidatesOrDefault(info: InferenceInfo) { + return !!(info.candidates || info.contraCandidates || hasTypeParameterDefault(info.typeParameter)); + } - 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; - } + 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; + } - if (operator === SyntaxKind.PlusEqualsToken) { - checkAssignmentOperator(resultType); - } - return resultType; - case SyntaxKind.LessThanToken: - case SyntaxKind.GreaterThanToken: - case SyntaxKind.LessThanEqualsToken: - case SyntaxKind.GreaterThanEqualsToken: - if (checkForDisallowedESSymbolOperand(operator)) { - leftType = getBaseTypeOfLiteralTypeForComparison(checkNonNullType(leftType, left)); - rightType = getBaseTypeOfLiteralTypeForComparison(checkNonNullType(rightType, right)); - reportOperatorErrorUnless((left, right) => { - if (isTypeAny(left) || isTypeAny(right)) { - return true; - } - const leftAssignableToNumber = isTypeAssignableTo(left, numberOrBigIntType); - const rightAssignableToNumber = isTypeAssignableTo(right, numberOrBigIntType); - return leftAssignableToNumber && rightAssignableToNumber || - !leftAssignableToNumber && !rightAssignableToNumber && areTypesComparable(left, right); - }); - } - return booleanType; - case SyntaxKind.EqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: - if (isLiteralExpressionOfObject(left) || isLiteralExpressionOfObject(right)) { - const eqType = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken; - error(errorNode, Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value, eqType ? "false" : "true"); - } - checkNaNEquality(errorNode, operator, left, right); - reportOperatorErrorUnless((left, right) => isTypeEqualityComparableTo(left, right) || isTypeEqualityComparableTo(right, left)); - return booleanType; + 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]; + } + } + } - 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; + 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); } - case SyntaxKind.BarBarToken: - case SyntaxKind.BarBarEqualsToken: { - const resultType = getTypeFacts(leftType) & TypeFacts.Falsy ? - getUnionType([getNonNullableType(removeDefinitelyFalsyTypes(leftType)), rightType], UnionReduction.Subtype) : - leftType; - if (operator === SyntaxKind.BarBarEqualsToken) { - checkAssignmentOperator(rightType); - } - return resultType; + else { + result.push(tp); } - 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; + } + if (newTypeParameters) { + const mapper = createTypeMapper(oldTypeParameters!, newTypeParameters); + for (const tp of newTypeParameters) { + tp.mapper = mapper; } - 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 rightType; - } - case SyntaxKind.CommaToken: - if (!compilerOptions.allowUnreachableCode && isSideEffectFree(left) && !isIndirectCall(left.parent as BinaryExpression)) { - 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; + } + return result; + } - default: - return Debug.fail(); + 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 as string).length; + while (len > 1 && (baseName as string).charCodeAt(len - 1) >= CharacterCodes._0 && (baseName as string).charCodeAt(len - 1) <= CharacterCodes._9) len--; + const s = (baseName as string).slice(0, len); + for (let index = 1; true; index++) { + const augmentedName = (s + index as __String); + if (!hasTypeParameterByName(typeParameters, augmentedName)) { + return augmentedName; + } } + } - function bothAreBigIntLike(left: Type, right: Type): boolean { - return isTypeAssignableToKind(left, TypeFlags.BigIntLike) && isTypeAssignableToKind(right, TypeFlags.BigIntLike); + function getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) { + const signature = getSingleCallSignature(funcType); + if (signature && !signature.typeParameters) { + return getReturnTypeOfSignature(signature); } + } - 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, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ false); - if (symbol?.declarations && symbol.declarations.some(isJSDocTypedefTag)) { - addDuplicateDeclarationErrorsForSymbols(symbol, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name), prop); - addDuplicateDeclarationErrorsForSymbols(prop, Diagnostics.Duplicate_identifier_0, unescapeLeadingUnderscores(name), symbol); - } - } - } + 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; + } - // Return true for "indirect calls", (i.e. `(0, x.f)(...)` or `(0, eval)(...)`), which prevents passing `this`. - function isIndirectCall(node: BinaryExpression): boolean { - return node.parent.kind === SyntaxKind.ParenthesizedExpression && - isNumericLiteral(node.left) && - node.left.text === "0" && - (isCallExpression(node.parent.parent) && node.parent.parent.expression === node.parent || node.parent.parent.kind === SyntaxKind.TaggedTemplateExpression) && - // special-case for "eval" because it's the only non-access case where an indirect call actually affects behavior. - (isAccessExpression(node.right) || isIdentifier(node.right) && node.right.escapedText === "eval"); + function getQuickTypeOfExpression(node: Expression): Type | undefined { + let expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); + if (isJSDocTypeAssertion(expr)) { + const type = getJSDocTypeAssertionType(expr); + if (!isConstTypeReference(type)) { + return getTypeFromTypeNode(type); + } + } + expr = skipParentheses(node); + if (isAwaitExpression(expr)) { + const type = getQuickTypeOfExpression(expr.expression); + return type ? getAwaitedType(type) : undefined; } + // 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, /*requireStringLiteralLikeArgument*/ true) && !isSymbolOrSymbolForCall(expr)) { + return isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) : + getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression)); + } + else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) { + return getTypeFromTypeNode((expr as TypeAssertion).type); + } + else if (isLiteralExpression(node) || isBooleanLiteral(node)) { + return checkExpression(node); + } + return undefined; + } - // Return true if there was no error, false if there was an error. - function checkForDisallowedESSymbolOperand(operator: PunctuationSyntaxKind): boolean { - const offendingSymbolOperand = - maybeTypeOfKindConsideringBaseConstraint(leftType, TypeFlags.ESSymbolLike) ? left : - maybeTypeOfKindConsideringBaseConstraint(rightType, TypeFlags.ESSymbolLike) ? right : - 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; + } + pushContextualType(node, anyType, /*isCache*/ false); + const type = links.contextFreeType = checkExpression(node, CheckMode.SkipContextSensitive); + popContextualType(); + return type; + } - if (offendingSymbolOperand) { - error(offendingSymbolOperand, Diagnostics.The_0_operator_cannot_be_applied_to_type_symbol, tokenToString(operator)); - return false; - } + 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, path: (node as TracingNode).tracingPath }); + 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; + } - return true; + 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 as PropertyAccessExpression).expression === node) || + (node.parent.kind === SyntaxKind.ElementAccessExpression && (node.parent as ElementAccessExpression).expression === node) || + ((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(node as Identifier) || + (node.parent.kind === SyntaxKind.TypeQuery && (node.parent as TypeQueryNode).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); } - function getSuggestedBooleanOperator(operator: SyntaxKind): PunctuationSyntaxKind | 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; + if (getIsolatedModules(compilerOptions)) { + 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_0_is_enabled, isolatedModulesLikeFlagName); } } + } - function checkAssignmentOperator(valueType: Type): void { - if (isAssignmentOperator(operator)) { - addLazyDiagnostic(checkAssignmentOperatorWorker); + function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { + if (hasJSDocNodes(node)) { + if (isJSDocSatisfiesExpression(node)) { + return checkSatisfiesExpressionWorker(node.expression, getJSDocSatisfiesExpressionType(node), checkMode); } + if (isJSDocTypeAssertion(node)) { + return checkAssertionWorker(node, checkMode); + } + } + return checkExpression(node.expression, checkMode); + } - function checkAssignmentOperatorWorker() { - let assigneeType = leftType; - - // getters can be a subtype of setters, so to check for assignability we use the setter's type instead - if (isCompoundAssignment(operatorToken.kind) && left.kind === SyntaxKind.PropertyAccessExpression) { - assigneeType = checkPropertyAccessExpression(left as PropertyAccessExpression, /*checkMode*/ undefined, /*writeOnly*/ true); + function getOperatorExtensionsForSymbols(name: string, symbols: readonly Symbol[]): Signature[] { + const signatures = new Set(); + for (const target of symbols) { + if (typeSymbolCache.has(target)) { + for (const tag of typeSymbolCache.get(target)!) { + if (operatorCache.has(tag)) { + const cache = operatorCache.get(tag) + const extensions = cache?.get(name); + if (extensions) { + for (const extension of extensions) { + for (const signature of getSignaturesOfType(getTypeOfSymbol(extension.patched), SignatureKind.Call)) { + signatures.add(signature) + } + } + } + } } + } + } + return arrayFrom(signatures.values()); + } + function getOperatorExtensionsForTypes(name: string, types: Type[]): Signature[] { + const symbols = new Set(); + for (const type of types) { + collectRelevantSymbols(type).forEach((symbol) => { + symbols.add(symbol) + }) + } + return getOperatorExtensionsForSymbols(name, arrayFrom(symbols.values())); + } - // 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) - ) { - let headMessage: DiagnosticMessage | undefined; - if (exactOptionalPropertyTypes && isPropertyAccessExpression(left) && maybeTypeOfKind(valueType, TypeFlags.Undefined)) { - const target = getTypeOfPropertyOfType(getTypeOfExpression(left.expression), left.name.escapedText); - if (isExactOptionalPropertyMismatch(valueType, target)) { - headMessage = Diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target; - } + function checkTsPlusOperator(left: Expression, right: Expression): readonly [Type, Type] | undefined { + // potentially captured by resolution + let lastSignature: Signature | undefined; + const resolutionDiagnostics = new Map readonly Diagnostic[]>(); + // not captured + const parent = right.parent as BinaryExpression; + const links = getNodeLinks(parent.operatorToken); + const alreadyResolved = links.resolvedSignature; + if (alreadyResolved && alreadyResolved === resolvingSignature) { + return; // in resolution + } + if (alreadyResolved) { + return [getTypeAtPosition(alreadyResolved, 0), getTypeAtPosition(alreadyResolved, 1)]; + } + if (links.isTsPlusOperatorToken === false) { + return; // already checked and not a custom operator + } + links.resolvedSignature = resolvingSignature; + const operator = invertedBinaryOp[parent.operatorToken.kind as keyof typeof invertedBinaryOp] as string | undefined; + if (operator) { + let resolved: Signature | undefined; + const leftType = getTypeOfNode(parent.left); + const leftOperators = getOperatorExtensionsForTypes(operator, [leftType]); + resolved = resolveTsPlusOperator(left, right, leftOperators); + if (!resolved) { + const rightType = checkExpression(right, CheckMode.Normal); + const rightOperators = getOperatorExtensionsForTypes(operator, [rightType]); + resolved = resolveTsPlusOperator(left, right, rightOperators); + } + if (resolved) { + links.resolvedSignature = resolved; + links.isTsPlusOperatorToken = true; + return [getTypeAtPosition(resolved, 0), getTypeAtPosition(resolved, 1)] as const; + } + else { + if (lastSignature) { + links.resolvedSignature = lastSignature; + links.isTsPlusOperatorToken = true; + const diagnosticsForSignature = resolutionDiagnostics.get(lastSignature); + if (diagnosticsForSignature) { + diagnosticsForSignature().forEach((diagnostic) => diagnostics.add(diagnostic)); + links.resolvedSignature = undefined; + } + return [errorType, errorType] as const; + } + } + } + links.isTsPlusOperatorToken = false; + return; + function resolveTsPlusOperator(left: Expression, right: Expression, signatures: Signature[]) { + for (let signature of signatures) { + if (resolutionDiagnostics.has(signature)) { + continue; + } + lastSignature = signature; + if (signature.typeParameters) { + const context = createInferenceContext(signature.typeParameters, signature, InferenceFlags.None) + const leftParam = unionIfLazy(getTypeAtPosition(signature, 0)); + const leftType = checkExpressionWithContextualType(left, leftParam, context, CheckMode.Normal); + inferTypes(context.inferences, leftType, leftParam); + const rightParam = unionIfLazy(getTypeAtPosition(signature, 1)); + const rightType = checkExpressionWithContextualType(right, rightParam, context, CheckMode.Normal); + inferTypes(context.inferences, rightType, rightParam); + const instantiated = getSignatureInstantiation(signature, getInferredTypes(context), /** isJavascript */ false); + const diagnostics = getSignatureApplicabilityError( + undefined, + [left, right], + instantiated, + assignableRelation, + CheckMode.Normal, + false, + undefined + ); + if (diagnostics) { + const leftParam = getTypeAtPosition(instantiated, 0); + const rightParam = getTypeAtPosition(instantiated, 1); + resolutionDiagnostics.set(signature, () => { + const diags: Diagnostic[] = []; + if (!isTypeAssignableTo(rightType, rightParam)) { + diags.push(error(right, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(rightType), typeToString(rightParam))); + } + if (!isTypeAssignableTo(leftType, leftParam)) { + diags.push(error(right, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(rightType), typeToString(rightParam))); + } + if (diags.length === 0) { + Debug.fail("Cannot find diagnostics"); + } + return diags; + }); + } else { + return instantiated; + } + } + else { + const diagnostics = getSignatureApplicabilityError( + undefined, + [left, right], + signature, + assignableRelation, + CheckMode.Normal, + false, + undefined + ); + if (diagnostics) { + resolutionDiagnostics.set(signature, () => { + const diags: Diagnostic[] = []; + const leftParam = getTypeAtPosition(signature, 0); + const rightParam = getTypeAtPosition(signature, 1); + const leftType = getTypeOfNode(left); + const rightType = getTypeOfNode(right); + if (!isTypeAssignableTo(rightType, rightParam)) { + diags.push(error(right, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(rightType), typeToString(rightParam))); + } + if (!isTypeAssignableTo(leftType, leftParam)) { + diags.push(error(right, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(rightType), typeToString(rightParam))); + } + if (diags.length === 0) { + Debug.fail("Cannot find diagnostics"); + } + return diags; + }); + } else { + return signature; } - // to avoid cascading errors check assignability only if 'isReference' check succeeded and no errors were reported - checkTypeAssignableToAndOptionallyElaborate(valueType, assigneeType, left, right, headMessage); } } } + } - 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; + function checkExpressionWorker(node: Expression | QualifiedName, checkMode: CheckMode | undefined, forceTuple?: boolean): Type { + if (node.kind !== SyntaxKind.QualifiedName && node.parent && node.parent.kind === SyntaxKind.BinaryExpression) { + const result = checkTsPlusOperator((node.parent as BinaryExpression).left, (node.parent as BinaryExpression).right); + if (result) { + return node === (node.parent as BinaryExpression).right ? result[1] : result[0]; } } - - /** - * Returns true if an error is reported - */ - function reportOperatorErrorUnless(typesAreCompatible: (left: Type, right: Type) => boolean): boolean { - if (!typesAreCompatible(leftType, rightType)) { - reportOperatorError(typesAreCompatible); - return true; + if (node.kind === SyntaxKind.BinaryExpression) { + checkTsPlusOperator((node as BinaryExpression).left, (node as BinaryExpression).right); + const links = getNodeLinks((node as BinaryExpression).operatorToken); + if (links.isTsPlusOperatorToken) { + return links.resolvedSignature ? getReturnTypeOfSignature(links.resolvedSignature) : errorType; } - return false; } - - function reportOperatorError(isRelated?: (left: Type, right: Type) => boolean) { - let wouldWorkWithAwait = false; - const errNode = errorNode || operatorToken; - if (isRelated) { - const awaitedLeftType = getAwaitedTypeNoAlias(leftType); - const awaitedRightType = getAwaitedTypeNoAlias(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, - ); + 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(); } } - - function tryGiveBetterPrimaryError(errNode: Node, maybeMissingAwait: boolean, leftStr: string, rightStr: string) { - switch (operatorToken.kind) { - case SyntaxKind.EqualsEqualsEqualsToken: - case SyntaxKind.EqualsEqualsToken: - case SyntaxKind.ExclamationEqualsEqualsToken: - case SyntaxKind.ExclamationEqualsToken: - return errorAndMaybeSuggestAwait( - errNode, - maybeMissingAwait, - Diagnostics.This_comparison_appears_to_be_unintentional_because_the_types_0_and_1_have_no_overlap, - leftStr, rightStr); - default: - return undefined; - } + switch (kind) { + case SyntaxKind.Identifier: + return checkIdentifier(node as Identifier, checkMode); + case SyntaxKind.PrivateIdentifier: + return checkPrivateIdentifierExpression(node as PrivateIdentifier); + case SyntaxKind.ThisKeyword: + return checkThisExpression(node); + case SyntaxKind.SuperKeyword: + return checkSuperExpression(node); + case SyntaxKind.NullKeyword: + return nullWideningType; + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.StringLiteral: + return hasSkipDirectInferenceFlag(node) ? + anyType : + getFreshTypeOfLiteralType(getStringLiteralType((node as StringLiteralLike).text)); + case SyntaxKind.NumericLiteral: + checkGrammarNumericLiteral(node as NumericLiteral); + return getFreshTypeOfLiteralType(getNumberLiteralType(+(node as NumericLiteral).text)); + case SyntaxKind.BigIntLiteral: + checkGrammarBigIntLiteral(node as BigIntLiteral); + return getFreshTypeOfLiteralType(getBigIntLiteralType({ + negative: false, + base10Value: parsePseudoBigInt((node as BigIntLiteral).text) + })); + case SyntaxKind.TrueKeyword: + return trueType; + case SyntaxKind.FalseKeyword: + return falseType; + case SyntaxKind.TemplateExpression: + return checkTemplateExpression(node as TemplateExpression); + case SyntaxKind.RegularExpressionLiteral: + return globalRegExpType; + case SyntaxKind.ArrayLiteralExpression: + return checkArrayLiteral(node as ArrayLiteralExpression, checkMode, forceTuple); + case SyntaxKind.ObjectLiteralExpression: + return checkObjectLiteral(node as ObjectLiteralExpression, checkMode); + case SyntaxKind.PropertyAccessExpression: + return checkPropertyAccessExpression(node as PropertyAccessExpression, checkMode); + case SyntaxKind.QualifiedName: + return checkQualifiedName(node as QualifiedName, checkMode); + case SyntaxKind.ElementAccessExpression: + return checkIndexedAccess(node as ElementAccessExpression, checkMode); + case SyntaxKind.CallExpression: + if ((node as CallExpression).expression.kind === SyntaxKind.ImportKeyword) { + return checkImportCallExpression(node as ImportCall); + } + // falls through + case SyntaxKind.NewExpression: + return checkCallExpression(node as CallExpression, checkMode); + case SyntaxKind.TaggedTemplateExpression: + return checkTaggedTemplateExpression(node as TaggedTemplateExpression); + case SyntaxKind.ParenthesizedExpression: + return checkParenthesizedExpression(node as ParenthesizedExpression, checkMode); + case SyntaxKind.ClassExpression: + return checkClassExpression(node as ClassExpression); + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return checkFunctionExpressionOrObjectLiteralMethod(node as FunctionExpression | ArrowFunction, checkMode); + case SyntaxKind.TypeOfExpression: + return checkTypeOfExpression(node as TypeOfExpression); + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + return checkAssertion(node as AssertionExpression, checkMode); + case SyntaxKind.NonNullExpression: + return checkNonNullAssertion(node as NonNullExpression); + case SyntaxKind.ExpressionWithTypeArguments: + return checkExpressionWithTypeArguments(node as ExpressionWithTypeArguments); + case SyntaxKind.SatisfiesExpression: + return checkSatisfiesExpression(node as SatisfiesExpression); + case SyntaxKind.MetaProperty: + return checkMetaProperty(node as MetaProperty); + case SyntaxKind.DeleteExpression: + return checkDeleteExpression(node as DeleteExpression); + case SyntaxKind.VoidExpression: + return checkVoidExpression(node as VoidExpression); + case SyntaxKind.AwaitExpression: + return checkAwaitExpression(node as AwaitExpression); + case SyntaxKind.PrefixUnaryExpression: + return checkPrefixUnaryExpression(node as PrefixUnaryExpression); + case SyntaxKind.PostfixUnaryExpression: + return checkPostfixUnaryExpression(node as PostfixUnaryExpression); + case SyntaxKind.BinaryExpression: + return checkBinaryExpression(node as BinaryExpression, checkMode); + case SyntaxKind.ConditionalExpression: + return checkConditionalExpression(node as ConditionalExpression, checkMode); + case SyntaxKind.SpreadElement: + return checkSpreadExpression(node as SpreadElement, checkMode); + case SyntaxKind.OmittedExpression: + return undefinedWideningType; + case SyntaxKind.YieldExpression: + return checkYieldExpression(node as YieldExpression); + case SyntaxKind.SyntheticExpression: + return checkSyntheticExpression(node as SyntheticExpression); + case SyntaxKind.JsxExpression: + return checkJsxExpression(node as JsxExpression, checkMode); + case SyntaxKind.JsxElement: + return checkJsxElement(node as JsxElement, checkMode); + case SyntaxKind.JsxSelfClosingElement: + return checkJsxSelfClosingElement(node as JsxSelfClosingElement, checkMode); + case SyntaxKind.JsxFragment: + return checkJsxFragment(node as JsxFragment); + case SyntaxKind.JsxAttributes: + return checkJsxAttributes(node as JsxAttributes, checkMode); + case SyntaxKind.JsxOpeningElement: + Debug.fail("Shouldn't ever directly check a JsxOpeningElement"); } + return errorType; + } - function checkNaNEquality(errorNode: Node | undefined, operator: SyntaxKind, left: Expression, right: Expression) { - const isLeftNaN = isGlobalNaN(skipParentheses(left)); - const isRightNaN = isGlobalNaN(skipParentheses(right)); - if (isLeftNaN || isRightNaN) { - const err = error(errorNode, Diagnostics.This_condition_will_always_return_0, - tokenToString(operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.EqualsEqualsToken ? SyntaxKind.FalseKeyword : SyntaxKind.TrueKeyword)); - if (isLeftNaN && isRightNaN) return; - const operatorString = operator === SyntaxKind.ExclamationEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken ? tokenToString(SyntaxKind.ExclamationToken) : ""; - const location = isLeftNaN ? right : left; - const expression = skipParentheses(location); - addRelatedInfo(err, createDiagnosticForNode(location, Diagnostics.Did_you_mean_0, - `${operatorString}Number.isNaN(${isEntityNameExpression(expression) ? entityNameToString(expression) : "..."})`)); - } + // DECLARATION AND STATEMENT TYPE CHECKING + + function checkTypeParameter(node: TypeParameterDeclaration) { + // Grammar Checking + checkGrammarModifiers(node); + if (node.expression) { + grammarErrorOnFirstToken(node.expression, Diagnostics.Type_expected); } - function isGlobalNaN(expr: Expression): boolean { - if (isIdentifier(expr) && expr.escapedText === "NaN") { - const globalNaNSymbol = getGlobalNaNSymbol(); - return !!globalNaNSymbol && globalNaNSymbol === getResolvedSymbol(expr); - } - return false; + checkSourceElement(node.constraint); + checkSourceElement(node.default); + const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(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); } + checkNodeDeferred(node); + addLazyDiagnostic(() => checkTypeNameIsReserved(node.name, Diagnostics.Type_parameter_name_cannot_be_0)); } - 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; + function checkTypeParameterDeferred(node: TypeParameterDeclaration) { + if (isInterfaceDeclaration(node.parent) || isClassLike(node.parent) || isTypeAliasDeclaration(node.parent)) { + const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(node)); + const modifiers = getTypeParameterModifiers(typeParameter) & (ModifierFlags.In | ModifierFlags.Out); + if (modifiers) { + const symbol = getSymbolOfDeclaration(node.parent); + if (isTypeAliasDeclaration(node.parent) && !(getObjectFlags(getDeclaredTypeOfSymbol(symbol)) & (ObjectFlags.Anonymous | ObjectFlags.Mapped))) { + error(node, Diagnostics.Variance_annotations_are_only_supported_in_type_aliases_for_object_function_constructor_and_mapped_types); + } + else if (modifiers === ModifierFlags.In || modifiers === ModifierFlags.Out) { + tracing?.push(tracing.Phase.CheckTypes, "checkTypeParameterDeferred", { parent: getTypeId(getDeclaredTypeOfSymbol(symbol)), id: getTypeId(typeParameter) }); + const source = createMarkerType(symbol, typeParameter, modifiers === ModifierFlags.Out ? markerSubTypeForCheck : markerSuperTypeForCheck); + const target = createMarkerType(symbol, typeParameter, modifiers === ModifierFlags.Out ? markerSuperTypeForCheck : markerSubTypeForCheck); + const saveVarianceTypeParameter = typeParameter; + varianceTypeParameter = typeParameter; + checkTypeAssignableTo(source, target, node, Diagnostics.Type_0_is_not_assignable_to_type_1_as_implied_by_variance_annotation); + varianceTypeParameter = saveVarianceTypeParameter; + tracing?.pop(); + } + } } - return [ effectiveLeft, effectiveRight ]; } - function checkYieldExpression(node: YieldExpression): Type { - addLazyDiagnostic(checkYieldExpressionGrammar); - - 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; - } + 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). + checkGrammarModifiers(node); - 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); + 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); } - - // Generator functions prior to ES2015 require the __values helper - if (!isAsync && languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Values); + 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); } } - - // 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; - } - let type = getContextualIterationType(IterationTypeKind.Next, func); - if (!type) { - type = anyType; - addLazyDiagnostic(() => { - if (noImplicitAny && !expressionResultIsUnused(node)) { - const contextualType = getContextualType(node, /*contextFlags*/ undefined); - if (!contextualType || isTypeAny(contextualType)) { - error(node, Diagnostics.yield_expression_implicitly_results_in_an_any_type_because_its_containing_generator_lacks_a_return_type_annotation); - } - } - }); + if (!node.initializer && isOptionalDeclaration(node) && isBindingPattern(node.name) && (func as FunctionLikeDeclaration).body) { + error(node, Diagnostics.A_binding_pattern_parameter_cannot_be_optional_in_an_implementation_signature); } - return type; - - function checkYieldExpressionGrammar() { - if (!(node.flags & NodeFlags.YieldContext)) { - grammarErrorOnFirstToken(node, Diagnostics.A_yield_expression_is_only_allowed_in_a_generator_body); + 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 (isInParameterInitializerBeforeContainingFunction(node)) { - error(node, Diagnostics.yield_expressions_cannot_be_used_in_a_parameter_initializer); + if (func.kind === SyntaxKind.Constructor || func.kind === SyntaxKind.ConstructSignature || func.kind === SyntaxKind.ConstructorType) { + error(node, Diagnostics.A_constructor_cannot_have_a_this_parameter); } - } - } - - function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type { - const type = checkTruthinessExpression(node.condition); - checkTestingKnownTruthyCallableOrAwaitableType(node.condition, type, node.whenTrue); - const type1 = checkExpression(node.whenTrue, checkMode); - const type2 = checkExpression(node.whenFalse, checkMode); - return getUnionType([type1, type2], UnionReduction.Subtype); - } - - function isTemplateLiteralContext(node: Node): boolean { - const parent = node.parent; - return isParenthesizedExpression(parent) && isTemplateLiteralContext(parent) || - isElementAccessExpression(parent) && parent.argumentExpression === node; - } - - function checkTemplateExpression(node: TemplateExpression): Type { - const texts = [node.head.text]; - const types = []; - for (const span of node.templateSpans) { - const type = checkExpression(span.expression); - if (maybeTypeOfKindConsideringBaseConstraint(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); + 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); } - texts.push(span.literal.text); - types.push(isTypeAssignableTo(type, templateConstraintType) ? type : stringType); } - return isConstContext(node) || isTemplateLiteralContext(node) || someType(getContextualType(node, /*contextFlags*/ undefined) || unknownType, isTemplateLiteralContextualType) ? getTemplateLiteralType(texts, types) : stringType; - } - - function isTemplateLiteralContextualType(type: Type): boolean { - return !!(type.flags & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral) || - type.flags & TypeFlags.InstantiableNonPrimitive && maybeTypeOfKind(getBaseConstraintOfType(type) || unknownType, TypeFlags.StringLike)); - } - function getContextNode(node: Expression): Expression { - if (isJsxAttributes(node) && !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) + // 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); } - return node; } - function checkExpressionWithContextualType(node: Expression, contextualType: Type, inferenceContext: InferenceContext | undefined, checkMode: CheckMode): Type { - const contextNode = getContextNode(node); - pushContextualType(contextNode, contextualType, /*isCache*/ false); - pushInferenceContext(contextNode, inferenceContext); - const type = checkExpression(node, checkMode | CheckMode.Contextual | (inferenceContext ? CheckMode.Inferential : 0)); - // In CheckMode.Inferential we collect intra-expression inference sites to process before fixing any type - // parameters. This information is no longer needed after the call to checkExpression. - if (inferenceContext && inferenceContext.intraExpressionInferenceSites) { - inferenceContext.intraExpressionInferenceSites = undefined; + 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; } - // 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, /*contextFlags*/ undefined)) ? - getRegularTypeOfLiteralType(type) : type; - popInferenceContext(); - popContextualType(); - return result; - } - function checkExpressionCached(node: Expression | QualifiedName, checkMode?: CheckMode): Type { - if (checkMode) { - return checkExpression(node, checkMode); - } - const links = getNodeLinks(node); - if (!links.resolvedType) { - // 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; + const signature = getSignatureFromDeclaration(parent); + const typePredicate = getTypePredicateOfSignature(signature); + if (!typePredicate) { + return; } - return links.resolvedType; - } - function isTypeAssertion(node: Expression) { - node = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); - return node.kind === SyntaxKind.TypeAssertionExpression || - node.kind === SyntaxKind.AsExpression || - isJSDocTypeAssertion(node); - } + checkSourceElement(node.type); - function checkDeclarationInitializer( - declaration: HasExpressionInitializer, - checkMode: CheckMode, - contextualType?: Type | undefined - ) { - const initializer = getEffectiveInitializer(declaration)!; - if (isInJSFile(declaration)) { - const typeNode = tryGetJSDocSatisfiesTypeNode(declaration); - if (typeNode) { - return checkSatisfiesExpressionWorker(initializer, typeNode, checkMode); - } + const { parameterName } = node; + if (typePredicate.kind === TypePredicateKind.This || typePredicate.kind === TypePredicateKind.AssertsThis) { + getTypeFromThisTypeNode(parameterName as ThisTypeNode); } - const type = getQuickTypeOfExpression(initializer) || - (contextualType ? - checkExpressionWithContextualType(initializer, contextualType, /*inferenceContext*/ undefined, checkMode || CheckMode.Normal) - : checkExpressionCached(initializer, checkMode)); - 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); + 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); } } } - 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 (isEmptyLiteralType(widened)) { - reportImplicitAny(declaration, anyType); - return anyType; - } - else if (isEmptyArrayLiteralType(widened)) { - reportImplicitAny(declaration, anyArrayType); - return anyArrayType; - } + 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 as SignatureDeclaration; + if (node === parent.type) { + return parent; + } } - return widened; } - function isLiteralOfContextualType(candidateType: Type, contextualType: Type | undefined): boolean { - if (contextualType) { - if (contextualType.flags & TypeFlags.UnionOrIntersection) { - const types = (contextualType as UnionType).types; - return some(types, t => isLiteralOfContextualType(candidateType, t)); + function checkIfTypePredicateVariableIsDeclaredInBindingPattern( + pattern: BindingPattern, + predicateVariableNode: Node, + predicateVariableName: string) { + for (const element of pattern.elements) { + if (isOmittedExpression(element)) { + continue; } - 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); + + 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; + } } - // 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) || - isJSDocTypeAssertion(parent) && isConstTypeReference(getJSDocTypeAssertionType(parent)) || - isValidConstAssertionArgument(node) && isConstTypeVariable(getContextualType(node, ContextFlags.None)) || - (isParenthesizedExpression(parent) || isArrayLiteralExpression(parent) || isSpreadElement(parent)) && isConstContext(parent) || - (isPropertyAssignment(parent) || isShorthandPropertyAssignment(parent) || isTemplateSpan(parent)) && isConstContext(parent.parent); - } + 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 as FunctionLikeDeclaration); + } - function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode | undefined, forceTuple?: boolean): Type { - const type = checkExpression(node, checkMode, forceTuple); - return isConstContext(node) || isCommonJsExportedExpression(node) ? getRegularTypeOfLiteralType(type) : - isTypeAssertion(node) ? type : - getWidenedLiteralLikeTypeForContextualType(type, instantiateContextualType(getContextualType(node, /*contextFlags*/ undefined), node, /*contextFlags*/ undefined)); - } + const functionFlags = getFunctionFlags(node as FunctionLikeDeclaration); + 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); + } - 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); + // 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); + } } - return checkExpressionForMutableLocation(node.initializer, checkMode); - } + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + checkUnmatchedJSDocParameters(node); - function checkObjectLiteralMethod(node: MethodDeclaration, checkMode?: CheckMode): Type { - // Grammar checking - checkGrammarMethod(node); + forEach(node.parameters, checkParameter); - // 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); + // TODO(rbuckton): Should we start checking JSDoc types? + if (node.type) { + checkSourceElement(node.type); } - const uninstantiatedType = checkFunctionExpressionOrObjectLiteralMethod(node, checkMode); - return instantiateTypeWithSingleGenericCallSignature(node, uninstantiatedType, checkMode); - } + addLazyDiagnostic(checkSignatureDeclarationDiagnostics); - 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 as Expression, 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)); + function checkSignatureDeclarationDiagnostics() { + 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 as FunctionDeclaration); + 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 as FunctionLikeDeclaration, returnTypeNode); + } + } + if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) { + registerForUnusedIdentifiersCheck(node); } } - 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 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 isStaticMember = isStatic(member); + const name = member.name; + if (!name) { + continue; + } + const isPrivate = isPrivateIdentifier(name); + const privateStaticFlags = isPrivate && isStaticMember ? DeclarationMeaning.PrivateStatic : 0; + const names = + isPrivate ? privateIdentifiers : + isStaticMember ? staticNames : + instanceNames; - function hasInferenceCandidates(info: InferenceInfo) { - return !!(info.candidates || info.contraCandidates); - } + const memberName = name && getPropertyNameForPropertyNameNode(name); + if (memberName) { + switch (member.kind) { + case SyntaxKind.GetAccessor: + addName(names, name, memberName, DeclarationMeaning.GetAccessor | privateStaticFlags); + break; - function hasInferenceCandidatesOrDefault(info: InferenceInfo) { - return !!(info.candidates || info.contraCandidates || hasTypeParameterDefault(info.typeParameter)); - } + case SyntaxKind.SetAccessor: + addName(names, name, memberName, DeclarationMeaning.SetAccessor | privateStaticFlags); + break; - 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; - } + case SyntaxKind.PropertyDeclaration: + addName(names, name, memberName, DeclarationMeaning.GetOrSetAccessor | privateStaticFlags); + break; - 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]; + case SyntaxKind.MethodDeclaration: + addName(names, name, memberName, DeclarationMeaning.Method | privateStaticFlags); + break; + } + } } } - } - 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); + function addName(names: Map<__String, DeclarationMeaning>, location: Node, name: __String, meaning: DeclarationMeaning) { + const prev = names.get(name); + if (prev) { + // For private identifiers, do not allow mixing of static and instance members with the same name + if ((prev & DeclarationMeaning.PrivateStatic) !== (meaning & DeclarationMeaning.PrivateStatic)) { + error(location, Diagnostics.Duplicate_identifier_0_Static_and_instance_elements_cannot_share_the_same_private_name, getTextOfNode(location)); + } + else { + const prevIsMethod = !!(prev & DeclarationMeaning.Method); + const isMethod = !!(meaning & DeclarationMeaning.Method); + if (prevIsMethod || isMethod) { + if (prevIsMethod !== isMethod) { + error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); + } + // If this is a method/method duplication is might be an overload, so this will be handled when overloads are considered + } + else if (prev & meaning & ~DeclarationMeaning.PrivateStatic) { + error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); + } + else { + names.set(name, prev | meaning); + } + } } else { - result.push(tp); + names.set(name, meaning); } } - if (newTypeParameters) { - const mapper = createTypeMapper(oldTypeParameters!, newTypeParameters); - for (const tp of newTypeParameters) { - tp.mapper = mapper; + } + + /** + * 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 isStaticMember = isStatic(member); + if (isStaticMember && 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(getSymbolOfDeclaration(node)); + error(memberNameNode, message, memberName, className); + break; + } } } - return result; } - function hasTypeParameterByName(typeParameters: readonly TypeParameter[] | undefined, name: __String) { - return some(typeParameters, tp => tp.symbol.escapedName === name); - } + 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; + } - function getUniqueTypeParameterName(typeParameters: readonly TypeParameter[], baseName: __String) { - let len = (baseName as string).length; - while (len > 1 && (baseName as string).charCodeAt(len - 1) >= CharacterCodes._0 && (baseName as string).charCodeAt(len - 1) <= CharacterCodes._9) len--; - const s = (baseName as string).slice(0, len); - for (let index = 1; true; index++) { - const augmentedName = (s + index as __String); - if (!hasTypeParameterByName(typeParameters, augmentedName)) { - return augmentedName; + 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 getReturnTypeOfSingleNonGenericCallSignature(funcType: Type) { - const signature = getSingleCallSignature(funcType); - if (signature && !signature.typeParameters) { - return getReturnTypeOfSignature(signature); + function checkTypeForDuplicateIndexSignatures(node: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode) { + if (node.kind === SyntaxKind.InterfaceDeclaration) { + const nodeSymbol = getSymbolOfDeclaration(node); + // 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 && nodeSymbol.declarations.length > 0 && nodeSymbol.declarations[0] !== node) { + return; + } } - } - - 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; + // 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(getSymbolOfDeclaration(node)!); + if (indexSymbol?.declarations) { + const indexSignatureMap = new Map(); + for (const declaration of (indexSymbol.declarations as IndexSignatureDeclaration[])) { + if (declaration.parameters.length === 1 && declaration.parameters[0].type) { + forEachType(getTypeFromTypeNode(declaration.parameters[0].type), type => { + const entry = indexSignatureMap.get(getTypeId(type)); + if (entry) { + entry.declarations.push(declaration); + } + else { + indexSignatureMap.set(getTypeId(type), { type, declarations: [declaration] }); + } + }); + } } + indexSignatureMap.forEach(entry => { + if (entry.declarations.length > 1) { + for (const declaration of entry.declarations) { + error(declaration, Diagnostics.Duplicate_index_signature_for_type_0, typeToString(entry.type)); + } + } + }); } - 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): Type | undefined { - let expr = skipParentheses(node, /*excludeJSDocTypeAssertions*/ true); - if (isJSDocTypeAssertion(expr)) { - const type = getJSDocTypeAssertionType(expr); - if (!isConstTypeReference(type)) { - return getTypeFromTypeNode(type); - } + function checkPropertyDeclaration(node: PropertyDeclaration | PropertySignature) { + // Grammar checking + if (!checkGrammarModifiers(node) && !checkGrammarProperty(node)) checkGrammarComputedPropertyName(node.name); + checkVariableLikeDeclaration(node); + + setNodeLinksForPrivateIdentifierScope(node); + + // property signatures already report "initializer not allowed in ambient context" elsewhere + if (hasSyntacticModifier(node, ModifierFlags.Abstract) && node.kind === SyntaxKind.PropertyDeclaration && node.initializer) { + error(node, Diagnostics.Property_0_cannot_have_an_initializer_because_it_is_marked_abstract, declarationNameToString(node.name)); } - expr = skipParentheses(node); - if (isAwaitExpression(expr)) { - const type = getQuickTypeOfExpression(expr.expression); - return type ? getAwaitedType(type) : undefined; + } + + function checkPropertySignature(node: PropertySignature) { + if (isPrivateIdentifier(node.name)) { + error(node, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); } - // 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, /*requireStringLiteralLikeArgument*/ true) && !isSymbolOrSymbolForCall(expr)) { - return isCallChain(expr) ? getReturnTypeOfSingleNonGenericSignatureOfCallChain(expr) : - getReturnTypeOfSingleNonGenericCallSignature(checkNonNullExpression(expr.expression)); + return checkPropertyDeclaration(node); + } + + function checkMethodDeclaration(node: MethodDeclaration | MethodSignature) { + // Grammar checking + if (!checkGrammarMethod(node)) checkGrammarComputedPropertyName(node.name); + + if (isMethodDeclaration(node) && node.asteriskToken && isIdentifier(node.name) && idText(node.name) === "constructor") { + error(node.name, Diagnostics.Class_constructor_may_not_be_a_generator); } - else if (isAssertionExpression(expr) && !isConstTypeReference(expr.type)) { - return getTypeFromTypeNode((expr as TypeAssertion).type); + + // Grammar checking for modifiers is done inside the function checkGrammarFunctionLikeDeclaration + checkFunctionOrMethodDeclaration(node); + + // method signatures already report "implementation not allowed in ambient context" elsewhere + 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)); } - else if (isLiteralExpression(node) || isBooleanLiteral(node)) { - return checkExpression(node); + + // Private named methods are only allowed in class declarations + if (isPrivateIdentifier(node.name) && !getContainingClass(node)) { + error(node, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); } - return undefined; + + setNodeLinksForPrivateIdentifierScope(node); } - /** - * 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; + function setNodeLinksForPrivateIdentifierScope(node: PropertyDeclaration | PropertySignature | MethodDeclaration | MethodSignature | AccessorDeclaration) { + if (isPrivateIdentifier(node.name) && languageVersion < ScriptTarget.ESNext) { + for (let lexicalScope = getEnclosingBlockScopeContainer(node); !!lexicalScope; lexicalScope = getEnclosingBlockScopeContainer(lexicalScope)) { + getNodeLinks(lexicalScope).flags |= NodeCheckFlags.ContainsClassWithPrivateIdentifiers; + } + + // If this is a private element in a class expression inside the body of a loop, + // then we must use a block-scoped binding to store the additional variables required + // to transform private elements. + if (isClassExpression(node.parent)) { + const enclosingIterationStatement = getEnclosingIterationStatement(node.parent); + if (enclosingIterationStatement) { + getNodeLinks(node.name).flags |= NodeCheckFlags.BlockScopedBindingInLoop; + getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; + } + } } - pushContextualType(node, anyType, /*isCache*/ false); - const type = links.contextFreeType = checkExpression(node, CheckMode.SkipContextSensitive); - popContextualType(); - return type; } - 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, path: (node as TracingNode).tracingPath }); - 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 checkClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { + checkGrammarModifiers(node); + + forEachChild(node, checkSourceElement); } - 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 as PropertyAccessExpression).expression === node) || - (node.parent.kind === SyntaxKind.ElementAccessExpression && (node.parent as ElementAccessExpression).expression === node) || - ((node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.QualifiedName) && isInRightSideOfImportOrExportAssignment(node as Identifier) || - (node.parent.kind === SyntaxKind.TypeQuery && (node.parent as TypeQueryNode).exprName === node)) || - (node.parent.kind === SyntaxKind.ExportSpecifier); // We allow reexporting const enums + 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); - 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); + checkSourceElement(node.body); + + const symbol = getSymbolOfDeclaration(node); + const firstDeclaration = getDeclarationOfKind(symbol, node.kind); + + // Only type check the symbol once + if (node === firstDeclaration) { + checkFunctionOrConstructorSymbol(symbol); } - if (getIsolatedModules(compilerOptions)) { - 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_0_is_enabled, isolatedModulesLikeFlagName); - } + // exit early in the case of signature - super checks are not relevant to them + if (nodeIsMissing(node.body)) { + return; } - } - function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { - if (hasJSDocNodes(node)) { - if (isJSDocSatisfiesExpression(node)) { - return checkSatisfiesExpressionWorker(node.expression, getJSDocSatisfiesExpressionType(node), checkMode); - } - if (isJSDocTypeAssertion(node)) { - return checkAssertionWorker(node, checkMode); + addLazyDiagnostic(checkConstructorDeclarationDiagnostics); + + return; + + function isInstancePropertyWithInitializerOrPrivateIdentifierProperty(n: Node): boolean { + if (isPrivateIdentifierClassElementDeclaration(n)) { + return true; } + return n.kind === SyntaxKind.PropertyDeclaration && + !isStatic(n) && + !!(n as PropertyDeclaration).initializer; } - return checkExpression(node.expression, checkMode); - } - function getOperatorExtensionsForSymbols(name: string, symbols: readonly Symbol[]): Signature[] { - const signatures = new Set(); - for (const target of symbols) { - if (typeSymbolCache.has(target)) { - for (const tag of typeSymbolCache.get(target)!) { - if (operatorCache.has(tag)) { - const cache = operatorCache.get(tag) - const extensions = cache?.get(name); - if (extensions) { - for (const extension of extensions) { - for (const signature of getSignaturesOfType(getTypeOfSymbol(extension.patched), SignatureKind.Call)) { - signatures.add(signature) + function checkConstructorDeclarationDiagnostics() { + // 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 as ClassDeclaration; + 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); + } + + // A super call must be root-level in a constructor 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 superCallShouldBeRootLevel = + (getEmitScriptTarget(compilerOptions) !== ScriptTarget.ESNext || !useDefineForClassFields) && + (some((node.parent as ClassDeclaration).members, isInstancePropertyWithInitializerOrPrivateIdentifierProperty) || + some(node.parameters, p => hasSyntacticModifier(p, ModifierFlags.ParameterPropertyModifier))); + + if (superCallShouldBeRootLevel) { + // Until we have better flow analysis, it is an error to place the super call within any kind of block or conditional + // See GH #8277 + if (!superCallIsRootLevelInConstructor(superCall, node.body!)) { + error(superCall, Diagnostics.A_super_call_must_be_a_root_level_statement_within_a_constructor_of_a_derived_class_that_contains_initialized_properties_parameter_properties_or_private_identifiers); + } + // Skip past any prologue directives to check statements for referring to 'super' or 'this' before a super call + else { + let superCallStatement: ExpressionStatement | undefined; + + for (const statement of node.body!.statements) { + if (isExpressionStatement(statement) && isSuperCall(skipOuterExpressions(statement.expression))) { + superCallStatement = statement; + break; + } + if (nodeImmediatelyReferencesSuperOrThis(statement)) { + break; } } + + // Until we have better flow analysis, it is an error to place the super call within any kind of block or conditional + // See GH #8277 + if (superCallStatement === undefined) { + error(node, Diagnostics.A_super_call_must_be_the_first_statement_in_the_constructor_to_refer_to_super_or_this_when_a_derived_class_contains_initialized_properties_parameter_properties_or_private_identifiers); + } } } } + else if (!classExtendsNull) { + error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call); + } } } - return arrayFrom(signatures.values()); - } - function getOperatorExtensionsForTypes(name: string, types: Type[]): Signature[] { - const symbols = new Set(); - for (const type of types) { - collectRelevantSymbols(type).forEach((symbol) => { - symbols.add(symbol) - }) - } - return getOperatorExtensionsForSymbols(name, arrayFrom(symbols.values())); } + function superCallIsRootLevelInConstructor(superCall: Node, body: Block) { + const superCallParent = walkUpParenthesizedExpressions(superCall.parent); + return isExpressionStatement(superCallParent) && superCallParent.parent === body; + } - function checkTsPlusOperator(left: Expression, right: Expression): readonly [Type, Type] | undefined { - // potentially captured by resolution - let lastSignature: Signature | undefined; - const resolutionDiagnostics = new Map readonly Diagnostic[]>(); - // not captured - const parent = right.parent as BinaryExpression; - const links = getNodeLinks(parent.operatorToken); - const alreadyResolved = links.resolvedSignature; - if (alreadyResolved && alreadyResolved === resolvingSignature) { - return; // in resolution + function nodeImmediatelyReferencesSuperOrThis(node: Node): boolean { + if (node.kind === SyntaxKind.SuperKeyword || node.kind === SyntaxKind.ThisKeyword) { + return true; } - if (alreadyResolved) { - return [getTypeAtPosition(alreadyResolved, 0), getTypeAtPosition(alreadyResolved, 1)]; + + if (isThisContainerOrFunctionBlock(node)) { + return false; } - if (links.isTsPlusOperatorToken === false) { - return; // already checked and not a custom operator + + return !!forEachChild(node, nodeImmediatelyReferencesSuperOrThis); + } + + function checkAccessorDeclaration(node: AccessorDeclaration) { + if (isIdentifier(node.name) && idText(node.name) === "constructor" && isClassLike(node.parent)) { + error(node.name, Diagnostics.Class_constructor_may_not_be_an_accessor); } - links.resolvedSignature = resolvingSignature; - const operator = invertedBinaryOp[parent.operatorToken.kind as keyof typeof invertedBinaryOp] as string | undefined; - if (operator) { - let resolved: Signature | undefined; - const leftType = getTypeOfNode(parent.left); - const leftOperators = getOperatorExtensionsForTypes(operator, [leftType]); - resolved = resolveTsPlusOperator(left, right, leftOperators); - if (!resolved) { - const rightType = checkExpression(right, CheckMode.Normal); - const rightOperators = getOperatorExtensionsForTypes(operator, [rightType]); - resolved = resolveTsPlusOperator(left, right, rightOperators); - } - if (resolved) { - links.resolvedSignature = resolved; - links.isTsPlusOperatorToken = true; - return [getTypeAtPosition(resolved, 0), getTypeAtPosition(resolved, 1)] as const; - } - else { - if (lastSignature) { - links.resolvedSignature = lastSignature; - links.isTsPlusOperatorToken = true; - const diagnosticsForSignature = resolutionDiagnostics.get(lastSignature); - if (diagnosticsForSignature) { - diagnosticsForSignature().forEach((diagnostic) => diagnostics.add(diagnostic)); - links.resolvedSignature = undefined; + addLazyDiagnostic(checkAccessorDeclarationDiagnostics); + checkSourceElement(node.body); + setNodeLinksForPrivateIdentifierScope(node); + + function checkAccessorDeclarationDiagnostics() { + // 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); } - return [errorType, errorType] as const; } } - } - links.isTsPlusOperatorToken = false; - return; - function resolveTsPlusOperator(left: Expression, right: Expression, signatures: Signature[]) { - for (let signature of signatures) { - if (resolutionDiagnostics.has(signature)) { - continue; - } - lastSignature = signature; - if (signature.typeParameters) { - const context = createInferenceContext(signature.typeParameters, signature, InferenceFlags.None) - const leftParam = unionIfLazy(getTypeAtPosition(signature, 0)); - const leftType = checkExpressionWithContextualType(left, leftParam, context, CheckMode.Normal); - inferTypes(context.inferences, leftType, leftParam); - const rightParam = unionIfLazy(getTypeAtPosition(signature, 1)); - const rightType = checkExpressionWithContextualType(right, rightParam, context, CheckMode.Normal); - inferTypes(context.inferences, rightType, rightParam); - const instantiated = getSignatureInstantiation(signature, getInferredTypes(context), /** isJavascript */ false); - const diagnostics = getSignatureApplicabilityError( - undefined, - [left, right], - instantiated, - assignableRelation, - CheckMode.Normal, - false, - undefined - ); - if (diagnostics) { - const leftParam = getTypeAtPosition(instantiated, 0); - const rightParam = getTypeAtPosition(instantiated, 1); - resolutionDiagnostics.set(signature, () => { - const diags: Diagnostic[] = []; - if (!isTypeAssignableTo(rightType, rightParam)) { - diags.push(error(right, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(rightType), typeToString(rightParam))); - } - if (!isTypeAssignableTo(leftType, leftParam)) { - diags.push(error(right, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(rightType), typeToString(rightParam))); - } - if (diags.length === 0) { - Debug.fail("Cannot find diagnostics"); - } - return diags; - }); - } else { - return instantiated; + // 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 (hasBindableName(node)) { + // TypeScript 1.0 spec (April 2014): 8.4.3 + // Accessors for the same member name must specify the same accessibility. + const symbol = getSymbolOfDeclaration(node); + const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); + const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); + if (getter && setter && !(getNodeCheckFlags(getter) & NodeCheckFlags.TypeChecked)) { + getNodeLinks(getter).flags |= NodeCheckFlags.TypeChecked; + const getterFlags = getEffectiveModifierFlags(getter); + const setterFlags = getEffectiveModifierFlags(setter); + if ((getterFlags & ModifierFlags.Abstract) !== (setterFlags & ModifierFlags.Abstract)) { + error(getter.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); + error(setter.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); } - } - else { - const diagnostics = getSignatureApplicabilityError( - undefined, - [left, right], - signature, - assignableRelation, - CheckMode.Normal, - false, - undefined - ); - if (diagnostics) { - resolutionDiagnostics.set(signature, () => { - const diags: Diagnostic[] = []; - const leftParam = getTypeAtPosition(signature, 0); - const rightParam = getTypeAtPosition(signature, 1); - const leftType = getTypeOfNode(left); - const rightType = getTypeOfNode(right); - if (!isTypeAssignableTo(rightType, rightParam)) { - diags.push(error(right, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(rightType), typeToString(rightParam))); - } - if (!isTypeAssignableTo(leftType, leftParam)) { - diags.push(error(right, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(rightType), typeToString(rightParam))); - } - if (diags.length === 0) { - Debug.fail("Cannot find diagnostics"); - } - return diags; - }); - } else { - return signature; + if (((getterFlags & ModifierFlags.Protected) && !(setterFlags & (ModifierFlags.Protected | ModifierFlags.Private))) || + ((getterFlags & ModifierFlags.Private) && !(setterFlags & ModifierFlags.Private))) { + error(getter.name, Diagnostics.A_get_accessor_must_be_at_least_as_accessible_as_the_setter); + error(setter.name, Diagnostics.A_get_accessor_must_be_at_least_as_accessible_as_the_setter); } } } + const returnType = getTypeOfAccessors(getSymbolOfDeclaration(node)); + if (node.kind === SyntaxKind.GetAccessor) { + checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); + } } } - function checkExpressionWorker(node: Expression | QualifiedName, checkMode: CheckMode | undefined, forceTuple?: boolean): Type { - if (node.kind !== SyntaxKind.QualifiedName && node.parent && node.parent.kind === SyntaxKind.BinaryExpression) { - const result = checkTsPlusOperator((node.parent as BinaryExpression).left, (node.parent as BinaryExpression).right); - if (result) { - return node === (node.parent as BinaryExpression).right ? result[1] : result[0]; - } - } - if (node.kind === SyntaxKind.BinaryExpression) { - checkTsPlusOperator((node as BinaryExpression).left, (node as BinaryExpression).right); - const links = getNodeLinks((node as BinaryExpression).operatorToken); - if (links.isTsPlusOperatorToken) { - return links.resolvedSignature ? getReturnTypeOfSignature(links.resolvedSignature) : errorType; - } - } - 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 as Identifier, checkMode); - case SyntaxKind.PrivateIdentifier: - return checkPrivateIdentifierExpression(node as PrivateIdentifier); - case SyntaxKind.ThisKeyword: - return checkThisExpression(node); - case SyntaxKind.SuperKeyword: - return checkSuperExpression(node); - case SyntaxKind.NullKeyword: - return nullWideningType; - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.StringLiteral: - return hasSkipDirectInferenceFlag(node) ? - anyType : - getFreshTypeOfLiteralType(getStringLiteralType((node as StringLiteralLike).text)); - case SyntaxKind.NumericLiteral: - checkGrammarNumericLiteral(node as NumericLiteral); - return getFreshTypeOfLiteralType(getNumberLiteralType(+(node as NumericLiteral).text)); - case SyntaxKind.BigIntLiteral: - checkGrammarBigIntLiteral(node as BigIntLiteral); - return getFreshTypeOfLiteralType(getBigIntLiteralType({ - negative: false, - base10Value: parsePseudoBigInt((node as BigIntLiteral).text) - })); - case SyntaxKind.TrueKeyword: - return trueType; - case SyntaxKind.FalseKeyword: - return falseType; - case SyntaxKind.TemplateExpression: - return checkTemplateExpression(node as TemplateExpression); - case SyntaxKind.RegularExpressionLiteral: - return globalRegExpType; - case SyntaxKind.ArrayLiteralExpression: - return checkArrayLiteral(node as ArrayLiteralExpression, checkMode, forceTuple); - case SyntaxKind.ObjectLiteralExpression: - return checkObjectLiteral(node as ObjectLiteralExpression, checkMode); - case SyntaxKind.PropertyAccessExpression: - return checkPropertyAccessExpression(node as PropertyAccessExpression, checkMode); - case SyntaxKind.QualifiedName: - return checkQualifiedName(node as QualifiedName, checkMode); - case SyntaxKind.ElementAccessExpression: - return checkIndexedAccess(node as ElementAccessExpression, checkMode); - case SyntaxKind.CallExpression: - if ((node as CallExpression).expression.kind === SyntaxKind.ImportKeyword) { - return checkImportCallExpression(node as ImportCall); - } - // falls through - case SyntaxKind.NewExpression: - return checkCallExpression(node as CallExpression, checkMode); - case SyntaxKind.TaggedTemplateExpression: - return checkTaggedTemplateExpression(node as TaggedTemplateExpression); - case SyntaxKind.ParenthesizedExpression: - return checkParenthesizedExpression(node as ParenthesizedExpression, checkMode); - case SyntaxKind.ClassExpression: - return checkClassExpression(node as ClassExpression); - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return checkFunctionExpressionOrObjectLiteralMethod(node as FunctionExpression | ArrowFunction, checkMode); - case SyntaxKind.TypeOfExpression: - return checkTypeOfExpression(node as TypeOfExpression); - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.AsExpression: - return checkAssertion(node as AssertionExpression, checkMode); - case SyntaxKind.NonNullExpression: - return checkNonNullAssertion(node as NonNullExpression); - case SyntaxKind.ExpressionWithTypeArguments: - return checkExpressionWithTypeArguments(node as ExpressionWithTypeArguments); - case SyntaxKind.SatisfiesExpression: - return checkSatisfiesExpression(node as SatisfiesExpression); - case SyntaxKind.MetaProperty: - return checkMetaProperty(node as MetaProperty); - case SyntaxKind.DeleteExpression: - return checkDeleteExpression(node as DeleteExpression); - case SyntaxKind.VoidExpression: - return checkVoidExpression(node as VoidExpression); - case SyntaxKind.AwaitExpression: - return checkAwaitExpression(node as AwaitExpression); - case SyntaxKind.PrefixUnaryExpression: - return checkPrefixUnaryExpression(node as PrefixUnaryExpression); - case SyntaxKind.PostfixUnaryExpression: - return checkPostfixUnaryExpression(node as PostfixUnaryExpression); - case SyntaxKind.BinaryExpression: - return checkBinaryExpression(node as BinaryExpression, checkMode); - case SyntaxKind.ConditionalExpression: - return checkConditionalExpression(node as ConditionalExpression, checkMode); - case SyntaxKind.SpreadElement: - return checkSpreadExpression(node as SpreadElement, checkMode); - case SyntaxKind.OmittedExpression: - return undefinedWideningType; - case SyntaxKind.YieldExpression: - return checkYieldExpression(node as YieldExpression); - case SyntaxKind.SyntheticExpression: - return checkSyntheticExpression(node as SyntheticExpression); - case SyntaxKind.JsxExpression: - return checkJsxExpression(node as JsxExpression, checkMode); - case SyntaxKind.JsxElement: - return checkJsxElement(node as JsxElement, checkMode); - case SyntaxKind.JsxSelfClosingElement: - return checkJsxSelfClosingElement(node as JsxSelfClosingElement, checkMode); - case SyntaxKind.JsxFragment: - return checkJsxFragment(node as JsxFragment); - case SyntaxKind.JsxAttributes: - return checkJsxAttributes(node as JsxAttributes, checkMode); - case SyntaxKind.JsxOpeningElement: - Debug.fail("Shouldn't ever directly check a JsxOpeningElement"); + function checkMissingDeclaration(node: Node) { + checkDecorators(node); + } + + function getEffectiveTypeArgumentAtIndex(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[], index: number): Type { + if (node.typeArguments && index < node.typeArguments.length) { + return getTypeFromTypeNode(node.typeArguments[index]); } - return errorType; + return getEffectiveTypeArguments(node, typeParameters)[index]; } - // DECLARATION AND STATEMENT TYPE CHECKING + function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] { + return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters, + getMinTypeArgumentCount(typeParameters), isInJSFile(node)); + } - function checkTypeParameter(node: TypeParameterDeclaration) { - // Grammar Checking - checkGrammarModifiers(node); - if (node.expression) { - grammarErrorOnFirstToken(node.expression, Diagnostics.Type_expected); + function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, 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; + } - checkSourceElement(node.constraint); - checkSourceElement(node.default); - const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(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); + function getTypeParametersForTypeAndSymbol(type: Type, symbol: Symbol) { + if (!isErrorType(type)) { + return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters || + (getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target.localTypeParameters : undefined); } - checkNodeDeferred(node); - addLazyDiagnostic(() => checkTypeNameIsReserved(node.name, Diagnostics.Type_parameter_name_cannot_be_0)); + return undefined; } - function checkTypeParameterDeferred(node: TypeParameterDeclaration) { - if (isInterfaceDeclaration(node.parent) || isClassLike(node.parent) || isTypeAliasDeclaration(node.parent)) { - const typeParameter = getDeclaredTypeOfTypeParameter(getSymbolOfDeclaration(node)); - const modifiers = getTypeParameterModifiers(typeParameter) & (ModifierFlags.In | ModifierFlags.Out); - if (modifiers) { - const symbol = getSymbolOfDeclaration(node.parent); - if (isTypeAliasDeclaration(node.parent) && !(getObjectFlags(getDeclaredTypeOfSymbol(symbol)) & (ObjectFlags.Anonymous | ObjectFlags.Mapped))) { - error(node, Diagnostics.Variance_annotations_are_only_supported_in_type_aliases_for_object_function_constructor_and_mapped_types); - } - else if (modifiers === ModifierFlags.In || modifiers === ModifierFlags.Out) { - tracing?.push(tracing.Phase.CheckTypes, "checkTypeParameterDeferred", { parent: getTypeId(getDeclaredTypeOfSymbol(symbol)), id: getTypeId(typeParameter) }); - const source = createMarkerType(symbol, typeParameter, modifiers === ModifierFlags.Out ? markerSubTypeForCheck : markerSuperTypeForCheck); - const target = createMarkerType(symbol, typeParameter, modifiers === ModifierFlags.Out ? markerSuperTypeForCheck : markerSubTypeForCheck); - const saveVarianceTypeParameter = typeParameter; - varianceTypeParameter = typeParameter; - checkTypeAssignableTo(source, target, node, Diagnostics.Type_0_is_not_assignable_to_type_1_as_implied_by_variance_annotation); - varianceTypeParameter = saveVarianceTypeParameter; - tracing?.pop(); - } + function getTypeParametersForTypeReferenceOrImport(node: TypeReferenceNode | ExpressionWithTypeArguments | ImportTypeNode) { + const type = getTypeFromTypeNode(node); + if (!isErrorType(type)) { + const symbol = getNodeLinks(node).resolvedSymbol; + if (symbol) { + return getTypeParametersForTypeAndSymbol(type, symbol); } } + return undefined; } - 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). - checkGrammarModifiers(node); + function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) { + checkGrammarTypeArguments(node, node.typeArguments); + if (node.kind === SyntaxKind.TypeReference && !isInJSFile(node) && !isInJSDoc(node) && node.typeArguments && node.typeName.end !== node.typeArguments.pos) { + // If there was a token between the type name and the type arguments, check if it was a DotToken + const sourceFile = getSourceFileOfNode(node); + if (scanTokenAtPosition(sourceFile, node.typeName.end) === SyntaxKind.DotToken) { + grammarErrorAtPos(node, skipTrivia(sourceFile.text, node.typeName.end), 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); + } + } + forEach(node.typeArguments, checkSourceElement); + checkTypeReferenceOrImport(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); + function checkTypeReferenceOrImport(node: TypeReferenceNode | ExpressionWithTypeArguments | ImportTypeNode) { + const type = getTypeFromTypeNode(node); + if (!isErrorType(type)) { + if (node.typeArguments) { + addLazyDiagnostic(() => { + const typeParameters = getTypeParametersForTypeReferenceOrImport(node); + if (typeParameters) { + checkTypeArgumentConstraints(node, typeParameters); + } + }); } - 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); + const symbol = getNodeLinks(node).resolvedSymbol; + if (symbol) { + if (some(symbol.declarations, d => isTypeDeclaration(d) && !!(d.flags & NodeFlags.Deprecated))) { + addDeprecatedSuggestion( + getDeprecatedSuggestionNode(node), + symbol.declarations!, + symbol.escapedName as string + ); + } } } - if (!node.initializer && isOptionalDeclaration(node) && isBindingPattern(node.name) && (func as FunctionLikeDeclaration).body) { - error(node, Diagnostics.A_binding_pattern_parameter_cannot_be_optional_in_an_implementation_signature); + } + + function getTypeArgumentConstraint(node: TypeNode): Type | undefined { + const typeReferenceNode = tryCast(node.parent, isTypeReferenceType); + if (!typeReferenceNode) return undefined; + const typeParameters = getTypeParametersForTypeReferenceOrImport(typeReferenceNode); + if (!typeParameters) return undefined; + 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); + addLazyDiagnostic(checkTypeLiteralDiagnostics); + + function checkTypeLiteralDiagnostics() { + const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); + checkIndexConstraints(type, type.symbol); + checkTypeForDuplicateIndexSignatures(node); + checkObjectTypeForDuplicateDeclarations(node); } - 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); + } + + 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 (const e of elementTypes) { + if (e.kind !== SyntaxKind.NamedTupleMember && hasNamedElement) { + grammarErrorOnNode(e, Diagnostics.Tuple_members_must_all_have_names_or_all_not_have_names); + break; } - if (func.kind === SyntaxKind.Constructor || func.kind === SyntaxKind.ConstructSignature || func.kind === SyntaxKind.ConstructorType) { - error(node, Diagnostics.A_constructor_cannot_have_a_this_parameter); + const flags = getTupleElementFlags(e); + if (flags & ElementFlags.Variadic) { + const type = getTypeFromTypeNode((e as RestTypeNode | NamedTupleMember).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; + } } - if (func.kind === SyntaxKind.ArrowFunction) { - error(node, Diagnostics.An_arrow_function_cannot_have_a_this_parameter); + else if (flags & ElementFlags.Rest) { + if (seenRestElement) { + grammarErrorOnNode(e, Diagnostics.A_rest_element_cannot_follow_another_rest_element); + break; + } + seenRestElement = true; } - if (func.kind === SyntaxKind.GetAccessor || func.kind === SyntaxKind.SetAccessor) { - error(node, Diagnostics.get_and_set_accessors_cannot_declare_this_parameters); + else if (flags & ElementFlags.Optional) { + if (seenRestElement) { + grammarErrorOnNode(e, Diagnostics.An_optional_element_cannot_follow_a_rest_element); + break; + } + seenOptionalElement = true; + } + else if (seenOptionalElement) { + grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element); + break; } } + forEach(node.elements, checkSourceElement); + getTypeFromTypeNode(node); + } - // 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 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 as IndexedAccessType).objectType; + const indexType = (type as IndexedAccessType).indexType; + if (isTypeAssignableTo(indexType, getIndexType(objectType, IndexFlags.None))) { + if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && + getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(objectType as MappedType) & 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, numberType) && 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 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; + function checkIndexedAccessType(node: IndexedAccessTypeNode) { + checkSourceElement(node.objectType); + checkSourceElement(node.indexType); + checkIndexedAccessIndexType(getTypeFromIndexedAccessTypeNode(node), node); + } + + function checkMappedType(node: MappedTypeNode) { + checkGrammarMappedType(node); + checkSourceElement(node.typeParameter); + checkSourceElement(node.nameType); + checkSourceElement(node.type); + + if (!node.type) { + reportImplicitAny(node, anyType); } - const signature = getSignatureFromDeclaration(parent); - const typePredicate = getTypePredicateOfSignature(signature); - if (!typePredicate) { - return; + const type = getTypeFromMappedTypeNode(node) as MappedType; + const nameType = getNameTypeFromMappedType(type); + if (nameType) { + checkTypeAssignableTo(nameType, keyofConstraintType, node.nameType); + } + else { + const constraintType = getConstraintTypeFromMappedType(type); + checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); + } + } + + function checkGrammarMappedType(node: MappedTypeNode) { + if (node.members?.length) { + return grammarErrorOnNode(node.members[0], Diagnostics.A_mapped_type_may_not_declare_properties_or_methods); } + } + + function checkThisType(node: ThisTypeNode) { + getTypeFromThisTypeNode(node); + } + function checkTypeOperator(node: TypeOperatorNode) { + checkGrammarTypeOperatorNode(node); checkSourceElement(node.type); + } - const { parameterName } = node; - if (typePredicate.kind === TypePredicateKind.This || typePredicate.kind === TypePredicateKind.AssertsThis) { - getTypeFromThisTypeNode(parameterName as ThisTypeNode); + function checkConditionalType(node: ConditionalTypeNode) { + forEachChild(node, checkSourceElement); + } + + function checkInferType(node: InferTypeNode) { + if (!findAncestor(node, n => n.parent && n.parent.kind === SyntaxKind.ConditionalType && (n.parent as ConditionalTypeNode).extendsType === n)) { + grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type); } - 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; + checkSourceElement(node.typeParameter); + const symbol = getSymbolOfDeclaration(node.typeParameter); + if (symbol.declarations && symbol.declarations.length > 1) { + const links = getSymbolLinks(symbol); + if (!links.typeParametersChecked) { + links.typeParametersChecked = true; + const typeParameter = getDeclaredTypeOfTypeParameter(symbol); + const declarations: TypeParameterDeclaration[] = getDeclarationsOfKind(symbol, SyntaxKind.TypeParameter); + if (!areTypeParametersIdentical(declarations, [typeParameter], decl => [decl])) { + // 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_constraints, name); } } - if (!hasReportedError) { - error(node.parameterName, Diagnostics.Cannot_find_parameter_0, typePredicate.parameterName); - } } } + registerForUnusedIdentifiersCheck(node); } - 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 as SignatureDeclaration; - if (node === parent.type) { - return parent; - } + 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 checkIfTypePredicateVariableIsDeclaredInBindingPattern( - pattern: BindingPattern, - predicateVariableNode: Node, - predicateVariableName: string) { - for (const element of pattern.elements) { - if (isOmittedExpression(element)) { - continue; - } + function checkImportType(node: ImportTypeNode) { + checkSourceElement(node.argument); - 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; + if (node.assertions) { + const override = getResolutionModeOverrideForClause(node.assertions.assertClause, grammarErrorOnNode); + if (override) { + if (!isNightly()) { + grammarErrorOnNode(node.assertions.assertClause, Diagnostics.resolution_mode_assertions_are_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next); + } + if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node16 && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) { + grammarErrorOnNode(node.assertions.assertClause, Diagnostics.resolution_mode_assertions_are_only_supported_when_moduleResolution_is_node16_or_nodenext); } } } + + checkTypeReferenceOrImport(node); } - function checkSignatureDeclaration(node: SignatureDeclaration) { - // Grammar checking - if (node.kind === SyntaxKind.IndexSignature) { - checkGrammarIndexSignature(node); + function checkNamedTupleMember(node: NamedTupleMember) { + if (node.dotDotDotToken && node.questionToken) { + grammarErrorOnNode(node, Diagnostics.A_tuple_member_cannot_be_both_optional_and_rest); } - // 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 as FunctionLikeDeclaration); + 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); + } - const functionFlags = getFunctionFlags(node as FunctionLikeDeclaration); - 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); - } + function isPrivateWithinAmbient(node: Node): boolean { + return (hasEffectiveModifier(node, ModifierFlags.Private) || isPrivateIdentifierClassElementDeclaration(node)) && !!(node.flags & NodeFlags.Ambient); + } - // Async functions prior to ES2017 require the __awaiter helper - if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async && languageVersion < ScriptTarget.ES2017) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Awaiter); - } + function getEffectiveDeclarationFlags(n: Declaration, flagsToCheck: ModifierFlags): ModifierFlags { + let flags = getCombinedModifierFlags(n); - // 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); + // 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; } - checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); - checkUnmatchedJSDocParameters(node); + return flags & flagsToCheck; + } - forEach(node.parameters, checkParameter); + function checkFunctionOrConstructorSymbol(symbol: Symbol): void { + addLazyDiagnostic(() => checkFunctionOrConstructorSymbolWorker(symbol)); + } - // TODO(rbuckton): Should we start checking JSDoc types? - if (node.type) { - checkSourceElement(node.type); + function checkFunctionOrConstructorSymbolWorker(symbol: Symbol): void { + 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]; } - addLazyDiagnostic(checkSignatureDeclarationDiagnostics); - - function checkSignatureDeclarationDiagnostics() { - 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 as FunctionDeclaration); - 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); + 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 { - // 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 (deviation & ModifierFlags.Ambient) { + error(getNameOfDeclaration(o), Diagnostics.Overload_signatures_must_all_be_ambient_or_non_ambient); } - } - else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { - checkAsyncFunctionReturnType(node as FunctionLikeDeclaration, returnTypeNode); - } - } - if (node.kind !== SyntaxKind.IndexSignature && node.kind !== SyntaxKind.JSDocFunctionType) { - registerForUnusedIdentifiersCheck(node); + 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 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); + 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); } - } + }); } - else { - const isStaticMember = isStatic(member); - const name = member.name; - if (!name) { - continue; - } - const isPrivate = isPrivateIdentifier(name); - const privateStaticFlags = isPrivate && isStaticMember ? DeclarationMeaning.PrivateStatic : 0; - const names = - isPrivate ? privateIdentifiers : - isStaticMember ? staticNames : - instanceNames; - - const memberName = name && getPropertyNameForPropertyNameNode(name); - if (memberName) { - switch (member.kind) { - case SyntaxKind.GetAccessor: - addName(names, name, memberName, DeclarationMeaning.GetAccessor | privateStaticFlags); - break; + } - case SyntaxKind.SetAccessor: - addName(names, name, memberName, DeclarationMeaning.SetAccessor | privateStaticFlags); - break; + 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; - case SyntaxKind.PropertyDeclaration: - addName(names, name, memberName, DeclarationMeaning.GetOrSetAccessor | privateStaticFlags); - break; + const declarations = symbol.declarations; + const isConstructor = (symbol.flags & SymbolFlags.Constructor) !== 0; - case SyntaxKind.MethodDeclaration: - addName(names, name, memberName, DeclarationMeaning.Method | privateStaticFlags); - break; - } - } + function reportImplementationExpectedError(node: SignatureDeclaration): void { + if (node.name && nodeIsMissing(node.name)) { + return; } - } - function addName(names: Map<__String, DeclarationMeaning>, location: Node, name: __String, meaning: DeclarationMeaning) { - const prev = names.get(name); - if (prev) { - // For private identifiers, do not allow mixing of static and instance members with the same name - if ((prev & DeclarationMeaning.PrivateStatic) !== (meaning & DeclarationMeaning.PrivateStatic)) { - error(location, Diagnostics.Duplicate_identifier_0_Static_and_instance_elements_cannot_share_the_same_private_name, getTextOfNode(location)); + let seen = false; + const subsequentNode = forEachChild(node.parent, c => { + if (seen) { + return c; } else { - const prevIsMethod = !!(prev & DeclarationMeaning.Method); - const isMethod = !!(meaning & DeclarationMeaning.Method); - if (prevIsMethod || isMethod) { - if (prevIsMethod !== isMethod) { - error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); + 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 as FunctionLikeDeclaration).name || subsequentNode; + const subsequentName = (subsequentNode as FunctionLikeDeclaration).name; + if (node.name && subsequentName && ( + // both are private identifiers + isPrivateIdentifier(node.name) && isPrivateIdentifier(subsequentName) && node.name.escapedText === subsequentName.escapedText || + // Both are computed property names + isComputedPropertyName(node.name) && isComputedPropertyName(subsequentName) && isTypeIdenticalTo(checkComputedPropertyName(node.name), checkComputedPropertyName(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) && + isStatic(node) !== isStatic(subsequentNode); + // 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 = isStatic(node) ? Diagnostics.Function_overload_must_be_static : Diagnostics.Function_overload_must_not_be_static; + error(errorNode, diagnostic); } - // If this is a method/method duplication is might be an overload, so this will be handled when overloads are considered - } - else if (prev & meaning & ~DeclarationMeaning.PrivateStatic) { - error(location, Diagnostics.Duplicate_identifier_0, getTextOfNode(location)); + return; } - else { - names.set(name, prev | meaning); + if (nodeIsPresent((subsequentNode as FunctionLikeDeclaration).body)) { + error(errorNode, Diagnostics.Function_implementation_name_must_be_0, declarationNameToString(node.name)); + return; } } } - else { - names.set(name, meaning); + const errorNode: Node = node.name || node; + if (isConstructor) { + error(errorNode, Diagnostics.Constructor_implementation_is_missing); } - } - } - - /** - * 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 isStaticMember = isStatic(member); - if (isStaticMember && 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(getSymbolOfDeclaration(node)); - error(memberNameNode, message, memberName, className); - break; + 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); } } } - } - 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; + let duplicateFunctionDeclaration = false; + let multipleConstructorImplementation = false; + let hasNonAmbientClass = false; + const functionDeclarations = [] as Declaration[]; + if (declarations) { + for (const current of declarations) { + const node = current as SignatureDeclaration | ClassDeclaration | ClassExpression; + 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 (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); + if ((node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression) && !inAmbientContext) { + hasNonAmbientClass = true; } - } - } - } - function checkTypeForDuplicateIndexSignatures(node: ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode) { - if (node.kind === SyntaxKind.InterfaceDeclaration) { - const nodeSymbol = getSymbolOfDeclaration(node); - // 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 && nodeSymbol.declarations.length > 0 && nodeSymbol.declarations[0] !== node) { - return; - } - } + 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); - // 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(getSymbolOfDeclaration(node)!); - if (indexSymbol?.declarations) { - const indexSignatureMap = new Map(); - for (const declaration of (indexSymbol.declarations as IndexSignatureDeclaration[])) { - if (declaration.parameters.length === 1 && declaration.parameters[0].type) { - forEachType(getTypeFromTypeNode(declaration.parameters[0].type), type => { - const entry = indexSignatureMap.get(getTypeId(type)); - if (entry) { - entry.declarations.push(declaration); + if (bodyIsPresent && bodyDeclaration) { + if (isConstructor) { + multipleConstructorImplementation = true; } else { - indexSignatureMap.set(getTypeId(type), { type, declarations: [declaration] }); + duplicateFunctionDeclaration = true; } - }); - } - } - indexSignatureMap.forEach(entry => { - if (entry.declarations.length > 1) { - for (const declaration of entry.declarations) { - error(declaration, Diagnostics.Duplicate_index_signature_for_type_0, typeToString(entry.type)); } - } - }); - } - } + else if (previousDeclaration?.parent === node.parent && previousDeclaration.end !== node.pos) { + reportImplementationExpectedError(previousDeclaration); + } - function checkPropertyDeclaration(node: PropertyDeclaration | PropertySignature) { - // Grammar checking - if (!checkGrammarModifiers(node) && !checkGrammarProperty(node)) checkGrammarComputedPropertyName(node.name); - checkVariableLikeDeclaration(node); + if (bodyIsPresent) { + if (!bodyDeclaration) { + bodyDeclaration = node as FunctionLikeDeclaration; + } + } + else { + hasOverloads = true; + } - setNodeLinksForPrivateIdentifierScope(node); + previousDeclaration = node; - // property signatures already report "initializer not allowed in ambient context" elsewhere - if (hasSyntacticModifier(node, ModifierFlags.Abstract) && node.kind === SyntaxKind.PropertyDeclaration && node.initializer) { - error(node, Diagnostics.Property_0_cannot_have_an_initializer_because_it_is_marked_abstract, declarationNameToString(node.name)); + if (!inAmbientContextOrInterface) { + lastSeenNonAmbientDeclaration = node as FunctionLikeDeclaration; + } + } + if (isInJSFile(current) && isFunctionLike(current) && current.jsDoc) { + for (const node of current.jsDoc) { + if (node.tags) { + for (const tag of node.tags) { + if (isJSDocOverloadTag(tag)) { + hasOverloads = true; + } + } + } + } + } + } } - } - function checkPropertySignature(node: PropertySignature) { - if (isPrivateIdentifier(node.name)) { - error(node, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + if (multipleConstructorImplementation) { + forEach(functionDeclarations, declaration => { + error(declaration, Diagnostics.Multiple_constructor_implementations_are_not_allowed); + }); } - return checkPropertyDeclaration(node); - } - - function checkMethodDeclaration(node: MethodDeclaration | MethodSignature) { - // Grammar checking - if (!checkGrammarMethod(node)) checkGrammarComputedPropertyName(node.name); - if (isMethodDeclaration(node) && node.asteriskToken && isIdentifier(node.name) && idText(node.name) === "constructor") { - error(node.name, Diagnostics.Class_constructor_may_not_be_a_generator); + if (duplicateFunctionDeclaration) { + forEach(functionDeclarations, declaration => { + error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Duplicate_function_implementation); + }); } - // Grammar checking for modifiers is done inside the function checkGrammarFunctionLikeDeclaration - checkFunctionOrMethodDeclaration(node); + if (hasNonAmbientClass && !isConstructor && symbol.flags & SymbolFlags.Function && declarations) { + const relatedDiagnostics = filter(declarations, d => d.kind === SyntaxKind.ClassDeclaration) + .map(d => createDiagnosticForNode(d, Diagnostics.Consider_adding_a_declare_modifier_to_this_class)); - // method signatures already report "implementation not allowed in ambient context" elsewhere - 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)); + forEach(declarations, declaration => { + const diagnostic = declaration.kind === SyntaxKind.ClassDeclaration + ? Diagnostics.Class_declaration_cannot_implement_overload_list_for_0 + : declaration.kind === SyntaxKind.FunctionDeclaration + ? Diagnostics.Function_with_bodies_can_only_merge_with_classes_that_are_ambient + : undefined; + if (diagnostic) { + addRelatedInfo( + error(getNameOfDeclaration(declaration) || declaration, diagnostic, symbolName(symbol)), + ...relatedDiagnostics + ); + } + }); } - // Private named methods are only allowed in class declarations - if (isPrivateIdentifier(node.name) && !getContainingClass(node)) { - error(node, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + // 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); } - setNodeLinksForPrivateIdentifierScope(node); - } - - function setNodeLinksForPrivateIdentifierScope(node: PropertyDeclaration | PropertySignature | MethodDeclaration | MethodSignature | AccessorDeclaration) { - if (isPrivateIdentifier(node.name) && languageVersion < ScriptTarget.ESNext) { - for (let lexicalScope = getEnclosingBlockScopeContainer(node); !!lexicalScope; lexicalScope = getEnclosingBlockScopeContainer(lexicalScope)) { - getNodeLinks(lexicalScope).flags |= NodeCheckFlags.ContainsClassWithPrivateIdentifiers; + if (hasOverloads) { + if (declarations) { + checkFlagAgreementBetweenOverloads(declarations, bodyDeclaration, flagsToCheck, someNodeFlags, allNodeFlags); + checkQuestionTokenAgreementBetweenOverloads(declarations, bodyDeclaration, someHaveQuestionToken, allHaveQuestionToken); } - // If this is a private element in a class expression inside the body of a loop, - // then we must use a block-scoped binding to store the additional variables required - // to transform private elements. - if (isClassExpression(node.parent)) { - const enclosingIterationStatement = getEnclosingIterationStatement(node.parent); - if (enclosingIterationStatement) { - getNodeLinks(node.name).flags |= NodeCheckFlags.BlockScopedBindingInLoop; - getNodeLinks(enclosingIterationStatement).flags |= NodeCheckFlags.LoopWithCapturedBlockScopedBinding; + if (bodyDeclaration) { + const signatures = getSignaturesOfSymbol(symbol); + const bodySignature = getSignatureFromDeclaration(bodyDeclaration); + for (const signature of signatures) { + if (!isImplementationCompatibleWithOverload(bodySignature, signature)) { + const errorNode = signature.declaration && isJSDocSignature(signature.declaration) + ? (signature.declaration.parent as JSDocOverloadTag | JSDocCallbackTag).tagName + : signature.declaration; + addRelatedInfo( + error(errorNode, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature), + createDiagnosticForNode(bodyDeclaration, Diagnostics.The_implementation_signature_is_declared_here) + ); + break; + } } } } } - function checkClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration) { - checkGrammarModifiers(node); - - forEachChild(node, checkSourceElement); + function checkExportsOnMergedDeclarations(node: Declaration): void { + addLazyDiagnostic(() => checkExportsOnMergedDeclarationsWorker(node)); } - 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 = getSymbolOfDeclaration(node); - const firstDeclaration = getDeclarationOfKind(symbol, node.kind); - - // Only type check the symbol once - if (node === firstDeclaration) { - checkFunctionOrConstructorSymbol(symbol); + function checkExportsOnMergedDeclarationsWorker(node: Declaration): void { + // 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 = getSymbolOfDeclaration(node)!; + if (!symbol.exportSymbol) { + // this is a pure local symbol (all declarations are non-exported) - no need to check anything + return; + } } - // exit early in the case of signature - super checks are not relevant to them - if (nodeIsMissing(node.body)) { + // run the check only for the first declaration in the list + if (getDeclarationOfKind(symbol, node.kind) !== node) { return; } - addLazyDiagnostic(checkConstructorDeclarationDiagnostics); - - 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); - function isInstancePropertyWithInitializerOrPrivateIdentifierProperty(n: Node): boolean { - if (isPrivateIdentifierClassElementDeclaration(n)) { - return true; + if (effectiveDeclarationFlags & ModifierFlags.Export) { + if (effectiveDeclarationFlags & ModifierFlags.Default) { + defaultExportedDeclarationSpaces |= declarationSpaces; + } + else { + exportedDeclarationSpaces |= declarationSpaces; + } + } + else { + nonExportedDeclarationSpaces |= declarationSpaces; } - return n.kind === SyntaxKind.PropertyDeclaration && - !isStatic(n) && - !!(n as PropertyDeclaration).initializer; } - function checkConstructorDeclarationDiagnostics() { - // 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 as ClassDeclaration; - 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); - } + // Spaces for anything not declared a 'default export'. + const nonDefaultExportedDeclarationSpaces = exportedDeclarationSpaces | nonExportedDeclarationSpaces; - // A super call must be root-level in a constructor 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 commonDeclarationSpacesForExportsAndLocals = exportedDeclarationSpaces & nonExportedDeclarationSpaces; + const commonDeclarationSpacesForDefaultAndNonDefault = defaultExportedDeclarationSpaces & nonDefaultExportedDeclarationSpaces; - const superCallShouldBeRootLevel = - (getEmitScriptTarget(compilerOptions) !== ScriptTarget.ESNext || !useDefineForClassFields) && - (some((node.parent as ClassDeclaration).members, isInstancePropertyWithInitializerOrPrivateIdentifierProperty) || - some(node.parameters, p => hasSyntacticModifier(p, ModifierFlags.ParameterPropertyModifier))); + if (commonDeclarationSpacesForExportsAndLocals || commonDeclarationSpacesForDefaultAndNonDefault) { + // declaration spaces for exported and non-exported declarations intersect + for (const d of symbol.declarations!) { + const declarationSpaces = getDeclarationSpaces(d); - if (superCallShouldBeRootLevel) { - // Until we have better flow analysis, it is an error to place the super call within any kind of block or conditional - // See GH #8277 - if (!superCallIsRootLevelInConstructor(superCall, node.body!)) { - error(superCall, Diagnostics.A_super_call_must_be_a_root_level_statement_within_a_constructor_of_a_derived_class_that_contains_initialized_properties_parameter_properties_or_private_identifiers); - } - // Skip past any prologue directives to check statements for referring to 'super' or 'this' before a super call - else { - let superCallStatement: ExpressionStatement | undefined; + 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)); + } + } + } - for (const statement of node.body!.statements) { - if (isExpressionStatement(statement) && isSuperCall(skipOuterExpressions(statement.expression))) { - superCallStatement = statement; - break; - } - if (nodeImmediatelyReferencesSuperOrThis(statement)) { - break; - } - } + function getDeclarationSpaces(decl: Declaration): DeclarationSpaces { + let d = decl as Node; + switch (d.kind) { + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: - // Until we have better flow analysis, it is an error to place the super call within any kind of block or conditional - // See GH #8277 - if (superCallStatement === undefined) { - error(node, Diagnostics.A_super_call_must_be_the_first_statement_in_the_constructor_to_refer_to_super_or_this_when_a_derived_class_contains_initialized_properties_parameter_properties_or_private_identifiers); - } - } + // 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: + case SyntaxKind.BinaryExpression: + const node = d as ExportAssignment | BinaryExpression; + const expression = isExportAssignment(node) ? node.expression : node.right; + // Export assigned entity name expressions act as aliases and should fall through, otherwise they export values + if (!isEntityNameExpression(expression)) { + return DeclarationSpaces.ExportValue; } - } - else if (!classExtendsNull) { - error(node, Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call); - } + d = 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(getSymbolOfDeclaration(d as ImportEqualsDeclaration | NamespaceImport | ImportClause | ExportAssignment | BinaryExpression)!); + 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; + case SyntaxKind.MethodSignature: + case SyntaxKind.PropertySignature: + return DeclarationSpaces.ExportType; + default: + return Debug.failBadSyntaxKind(d); } } } - function superCallIsRootLevelInConstructor(superCall: Node, body: Block) { - const superCallParent = walkUpParenthesizedExpressions(superCall.parent); - return isExpressionStatement(superCallParent) && superCallParent.parent === body; + function getAwaitedTypeOfPromise(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, ...args: DiagnosticArguments): Type | undefined { + const promisedType = getPromisedTypeOfPromise(type, errorNode); + return promisedType && getAwaitedType(promisedType, errorNode, diagnosticMessage, ...args); } - function nodeImmediatelyReferencesSuperOrThis(node: Node): boolean { - if (node.kind === SyntaxKind.SuperKeyword || node.kind === SyntaxKind.ThisKeyword) { - return true; - } + /** + * 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, thisTypeForErrorOut?: { value?: Type }): Type | undefined { + // + // { // type + // then( // thenFunction + // onfulfilled: ( // onfulfilledParameterType + // value: T // valueParameterType + // ) => any + // ): any; + // } + // - if (isThisContainerOrFunctionBlock(node)) { - return false; + if (isTypeAny(type)) { + return undefined; } - return !!forEachChild(node, nodeImmediatelyReferencesSuperOrThis); - } - - function checkAccessorDeclaration(node: AccessorDeclaration) { - if (isIdentifier(node.name) && idText(node.name) === "constructor" && isClassLike(node.parent)) { - error(node.name, Diagnostics.Class_constructor_may_not_be_an_accessor); + const typeAsPromise = type as PromiseOrAwaitableType; + if (typeAsPromise.promisedTypeOfPromise) { + return typeAsPromise.promisedTypeOfPromise; } - addLazyDiagnostic(checkAccessorDeclarationDiagnostics); - checkSourceElement(node.body); - setNodeLinksForPrivateIdentifierScope(node); - - function checkAccessorDeclarationDiagnostics() { - // 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 (hasBindableName(node)) { - // TypeScript 1.0 spec (April 2014): 8.4.3 - // Accessors for the same member name must specify the same accessibility. - const symbol = getSymbolOfDeclaration(node); - const getter = getDeclarationOfKind(symbol, SyntaxKind.GetAccessor); - const setter = getDeclarationOfKind(symbol, SyntaxKind.SetAccessor); - if (getter && setter && !(getNodeCheckFlags(getter) & NodeCheckFlags.TypeChecked)) { - getNodeLinks(getter).flags |= NodeCheckFlags.TypeChecked; - const getterFlags = getEffectiveModifierFlags(getter); - const setterFlags = getEffectiveModifierFlags(setter); - if ((getterFlags & ModifierFlags.Abstract) !== (setterFlags & ModifierFlags.Abstract)) { - error(getter.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); - error(setter.name, Diagnostics.Accessors_must_both_be_abstract_or_non_abstract); - } - if (((getterFlags & ModifierFlags.Protected) && !(setterFlags & (ModifierFlags.Protected | ModifierFlags.Private))) || - ((getterFlags & ModifierFlags.Private) && !(setterFlags & ModifierFlags.Private))) { - error(getter.name, Diagnostics.A_get_accessor_must_be_at_least_as_accessible_as_the_setter); - error(setter.name, Diagnostics.A_get_accessor_must_be_at_least_as_accessible_as_the_setter); - } - } - } - const returnType = getTypeOfAccessors(getSymbolOfDeclaration(node)); - if (node.kind === SyntaxKind.GetAccessor) { - checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnType); - } + if (isReferenceToType(type, getGlobalPromiseType(/*reportErrors*/ false))) { + return typeAsPromise.promisedTypeOfPromise = getTypeArguments(type as GenericType)[0]; } - } - function checkMissingDeclaration(node: Node) { - checkDecorators(node); - } - - function getEffectiveTypeArgumentAtIndex(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[], index: number): Type { - if (node.typeArguments && index < node.typeArguments.length) { - return getTypeFromTypeNode(node.typeArguments[index]); + // primitives with a `{ then() }` won't be unwrapped/adopted. + if (allTypesAssignableToKind(getBaseConstraintOrType(type), TypeFlags.Primitive | TypeFlags.Never)) { + return undefined; } - return getEffectiveTypeArguments(node, typeParameters)[index]; - } - function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] { - return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters, - getMinTypeArgumentCount(typeParameters), isInJSFile(node)); - } + const thenFunction = getTypeOfPropertyOfType(type, "then" as __String)!; // TODO: GH#18217 + if (isTypeAny(thenFunction)) { + return undefined; + } - function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, 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); + 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; } - return result; - } - function getTypeParametersForTypeAndSymbol(type: Type, symbol: Symbol) { - if (!isErrorType(type)) { - return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters || - (getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target.localTypeParameters : undefined); + let thisTypeForError: Type | undefined; + let candidates: Signature[] | undefined; + for (const thenSignature of thenSignatures) { + const thisType = getThisTypeOfSignature(thenSignature); + if (thisType && thisType !== voidType && !isTypeRelatedTo(type, thisType, subtypeRelation)) { + thisTypeForError = thisType; + } + else { + candidates = append(candidates, thenSignature); + } } - return undefined; - } - function getTypeParametersForTypeReferenceOrImport(node: TypeReferenceNode | ExpressionWithTypeArguments | ImportTypeNode) { - const type = getTypeFromTypeNode(node); - if (!isErrorType(type)) { - const symbol = getNodeLinks(node).resolvedSymbol; - if (symbol) { - return getTypeParametersForTypeAndSymbol(type, symbol); + if (!candidates) { + Debug.assertIsDefined(thisTypeForError); + if (thisTypeForErrorOut) { + thisTypeForErrorOut.value = thisTypeForError; + } + if (errorNode) { + error(errorNode, Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1, typeToString(type), typeToString(thisTypeForError)); } + return undefined; } - return undefined; - } - function checkTypeReferenceNode(node: TypeReferenceNode | ExpressionWithTypeArguments) { - checkGrammarTypeArguments(node, node.typeArguments); - if (node.kind === SyntaxKind.TypeReference && !isInJSFile(node) && !isInJSDoc(node) && node.typeArguments && node.typeName.end !== node.typeArguments.pos) { - // If there was a token between the type name and the type arguments, check if it was a DotToken - const sourceFile = getSourceFileOfNode(node); - if (scanTokenAtPosition(sourceFile, node.typeName.end) === SyntaxKind.DotToken) { - grammarErrorAtPos(node, skipTrivia(sourceFile.text, node.typeName.end), 1, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); - } + const onfulfilledParameterType = getTypeWithFacts(getUnionType(map(candidates, getTypeOfFirstParameterOfSignature)), TypeFacts.NEUndefinedOrNull); + if (isTypeAny(onfulfilledParameterType)) { + return undefined; } - forEach(node.typeArguments, checkSourceElement); - checkTypeReferenceOrImport(node); - } - function checkTypeReferenceOrImport(node: TypeReferenceNode | ExpressionWithTypeArguments | ImportTypeNode) { - const type = getTypeFromTypeNode(node); - if (!isErrorType(type)) { - if (node.typeArguments) { - addLazyDiagnostic(() => { - const typeParameters = getTypeParametersForTypeReferenceOrImport(node); - if (typeParameters) { - checkTypeArgumentConstraints(node, typeParameters); - } - }); - } - const symbol = getNodeLinks(node).resolvedSymbol; - if (symbol) { - if (some(symbol.declarations, d => isTypeDeclaration(d) && !!(d.flags & NodeFlags.Deprecated))) { - addDeprecatedSuggestion( - getDeprecatedSuggestionNode(node), - symbol.declarations!, - symbol.escapedName as string - ); - } + 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; } - } - function getTypeArgumentConstraint(node: TypeNode): Type | undefined { - const typeReferenceNode = tryCast(node.parent, isTypeReferenceType); - if (!typeReferenceNode) return undefined; - const typeParameters = getTypeParametersForTypeReferenceOrImport(typeReferenceNode); - if (!typeParameters) return undefined; - const constraint = getConstraintOfTypeParameter(typeParameters[typeReferenceNode.typeArguments!.indexOf(node)]); - return constraint && instantiateType(constraint, createTypeMapper(typeParameters, getEffectiveTypeArguments(typeReferenceNode, typeParameters))); + return typeAsPromise.promisedTypeOfPromise = getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature), UnionReduction.Subtype); } - function checkTypeQuery(node: TypeQueryNode) { - getTypeFromTypeQueryNode(node); + /** + * Gets the "awaited type" of a type. + * @param type The type to await. + * @param withAlias When `true`, wraps the "awaited type" in `Awaited` if needed. + * @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, withAlias: boolean, errorNode: Node, diagnosticMessage: DiagnosticMessage, ...args: DiagnosticArguments): Type { + const awaitedType = withAlias ? + getAwaitedType(type, errorNode, diagnosticMessage, ...args) : + getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, ...args); + return awaitedType || errorType; } - function checkTypeLiteral(node: TypeLiteralNode) { - forEach(node.members, checkSourceElement); - addLazyDiagnostic(checkTypeLiteralDiagnostics); - - function checkTypeLiteralDiagnostics() { - const type = getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode(node); - checkIndexConstraints(type, type.symbol); - checkTypeForDuplicateIndexSignatures(node); - checkObjectTypeForDuplicateDeclarations(node); + /** + * Determines whether a type is an object with a callable `then` member. + */ + function isThenableType(type: Type): boolean { + if (allTypesAssignableToKind(getBaseConstraintOrType(type), TypeFlags.Primitive | TypeFlags.Never)) { + // primitive types cannot be considered "thenable" since they are not objects. + return false; } + + const thenFunction = getTypeOfPropertyOfType(type, "then" as __String); + return !!thenFunction && getSignaturesOfType(getTypeWithFacts(thenFunction, TypeFacts.NEUndefinedOrNull), SignatureKind.Call).length > 0; } - function checkArrayType(node: ArrayTypeNode) { - checkSourceElement(node.elementType); + interface AwaitedTypeInstantiation extends Type { + _awaitedTypeBrand: never; + aliasSymbol: Symbol; + aliasTypeArguments: readonly Type[]; } - function checkTupleType(node: TupleTypeNode) { - const elementTypes = node.elements; - let seenOptionalElement = false; - let seenRestElement = false; - const hasNamedElement = some(elementTypes, isNamedTupleMember); - for (const e of elementTypes) { - 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 as RestTypeNode | NamedTupleMember).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) { - if (seenRestElement) { - grammarErrorOnNode(e, Diagnostics.A_rest_element_cannot_follow_another_rest_element); - break; - } - seenRestElement = true; - } - else if (flags & ElementFlags.Optional) { - if (seenRestElement) { - grammarErrorOnNode(e, Diagnostics.An_optional_element_cannot_follow_a_rest_element); - break; - } - seenOptionalElement = true; - } - else if (seenOptionalElement) { - grammarErrorOnNode(e, Diagnostics.A_required_element_cannot_follow_an_optional_element); - break; - } + function isAwaitedTypeInstantiation(type: Type): type is AwaitedTypeInstantiation { + if (type.flags & TypeFlags.Conditional) { + const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ false); + return !!awaitedSymbol && type.aliasSymbol === awaitedSymbol && type.aliasTypeArguments?.length === 1; } - forEach(node.elements, checkSourceElement); - getTypeFromTypeNode(node); + return false; } - function checkUnionOrIntersectionType(node: UnionOrIntersectionTypeNode) { - forEach(node.types, checkSourceElement); - getTypeFromTypeNode(node); + /** + * For a generic `Awaited`, gets `T`. + */ + function unwrapAwaitedType(type: Type) { + return type.flags & TypeFlags.Union ? mapType(type, unwrapAwaitedType) : + isAwaitedTypeInstantiation(type) ? type.aliasTypeArguments[0] : + type; } - 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 as IndexedAccessType).objectType; - const indexType = (type as IndexedAccessType).indexType; - if (isTypeAssignableTo(indexType, getIndexType(objectType, IndexFlags.None))) { - if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && - getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(objectType as MappedType) & 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, numberType) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { - return type; + function isAwaitedTypeNeeded(type: Type) { + // If this is already an `Awaited`, we shouldn't wrap it. This helps to avoid `Awaited>` in higher-order. + if (isTypeAny(type) || isAwaitedTypeInstantiation(type)) { + return false; } - 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; - } + + // We only need `Awaited` if `T` contains possibly non-primitive types. + if (isGenericObjectType(type)) { + const baseConstraint = getBaseConstraintOfType(type); + // We only need `Awaited` if `T` is a type variable that has no base constraint, or the base constraint of `T` is `any`, `unknown`, `{}`, `object`, + // or is promise-like. + if (baseConstraint ? + baseConstraint.flags & TypeFlags.AnyOrUnknown || isEmptyObjectType(baseConstraint) || someType(baseConstraint, isThenableType) : + maybeTypeOfKind(type, TypeFlags.TypeVariable)) { + return true; } } - 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); + return false; } - function checkMappedType(node: MappedTypeNode) { - checkGrammarMappedType(node); - checkSourceElement(node.typeParameter); - checkSourceElement(node.nameType); - checkSourceElement(node.type); - - if (!node.type) { - reportImplicitAny(node, anyType); + function tryCreateAwaitedType(type: Type): Type | undefined { + // Nothing to do if `Awaited` doesn't exist + const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ true); + if (awaitedSymbol) { + // Unwrap unions that may contain `Awaited`, otherwise its possible to manufacture an `Awaited | U>` where + // an `Awaited` would suffice. + return getTypeAliasInstantiation(awaitedSymbol, [unwrapAwaitedType(type)]); } - const type = getTypeFromMappedTypeNode(node) as MappedType; - const nameType = getNameTypeFromMappedType(type); - if (nameType) { - checkTypeAssignableTo(nameType, keyofConstraintType, node.nameType); - } - else { - const constraintType = getConstraintTypeFromMappedType(type); - checkTypeAssignableTo(constraintType, keyofConstraintType, getEffectiveConstraintOfTypeParameter(node.typeParameter)); - } + return undefined; } - function checkGrammarMappedType(node: MappedTypeNode) { - if (node.members?.length) { - return grammarErrorOnNode(node.members[0], Diagnostics.A_mapped_type_may_not_declare_properties_or_methods); - } - } + function createAwaitedTypeIfNeeded(type: Type): Type { + // We wrap type `T` in `Awaited` based on the following conditions: + // - `T` is not already an `Awaited`, and + // - `T` is generic, and + // - One of the following applies: + // - `T` has no base constraint, or + // - The base constraint of `T` is `any`, `unknown`, `object`, or `{}`, or + // - The base constraint of `T` is an object type with a callable `then` method. - function checkThisType(node: ThisTypeNode) { - getTypeFromThisTypeNode(node); - } + if (isAwaitedTypeNeeded(type)) { + const awaitedType = tryCreateAwaitedType(type); + if (awaitedType) { + return awaitedType; + } + } - function checkTypeOperator(node: TypeOperatorNode) { - checkGrammarTypeOperatorNode(node); - checkSourceElement(node.type); + Debug.assert(isAwaitedTypeInstantiation(type) || getPromisedTypeOfPromise(type) === undefined, "type provided should not be a non-generic 'promise'-like."); + return type; } - function checkConditionalType(node: ConditionalTypeNode) { - forEachChild(node, checkSourceElement); + /** + * 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, ...args: DiagnosticArguments): Type | undefined { + const awaitedType = getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, ...args); + return awaitedType && createAwaitedTypeIfNeeded(awaitedType); } - function checkInferType(node: InferTypeNode) { - if (!findAncestor(node, n => n.parent && n.parent.kind === SyntaxKind.ConditionalType && (n.parent as ConditionalTypeNode).extendsType === n)) { - grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type); + /** + * Gets the "awaited type" of a type without introducing an `Awaited` wrapper. + * + * @see {@link getAwaitedType} + */ + function getAwaitedTypeNoAlias(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, ...args: DiagnosticArguments): Type | undefined { + if (isTypeAny(type)) { + return type; } - checkSourceElement(node.typeParameter); - const symbol = getSymbolOfDeclaration(node.typeParameter); - if (symbol.declarations && symbol.declarations.length > 1) { - const links = getSymbolLinks(symbol); - if (!links.typeParametersChecked) { - links.typeParametersChecked = true; - const typeParameter = getDeclaredTypeOfTypeParameter(symbol); - const declarations: TypeParameterDeclaration[] = getDeclarationsOfKind(symbol, SyntaxKind.TypeParameter); - if (!areTypeParametersIdentical(declarations, [typeParameter], decl => [decl])) { - // 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_constraints, name); - } - } - } + + // If this is already an `Awaited`, just return it. This avoids `Awaited>` in higher-order + if (isAwaitedTypeInstantiation(type)) { + return type; } - 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); + // If we've already cached an awaited type, return a possible `Awaited` for it. + const typeAsAwaitable = type as PromiseOrAwaitableType; + if (typeAsAwaitable.awaitedTypeOfType) { + return typeAsAwaitable.awaitedTypeOfType; } - getTypeFromTypeNode(node); - } - - function checkImportType(node: ImportTypeNode) { - checkSourceElement(node.argument); - if (node.assertions) { - const override = getResolutionModeOverrideForClause(node.assertions.assertClause, grammarErrorOnNode); - if (override) { - if (!isNightly()) { - grammarErrorOnNode(node.assertions.assertClause, Diagnostics.resolution_mode_assertions_are_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next); - } - if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node16 && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) { - grammarErrorOnNode(node.assertions.assertClause, Diagnostics.resolution_mode_assertions_are_only_supported_when_moduleResolution_is_node16_or_nodenext); + // For a union, get a union of the awaited types of each constituent. + if (type.flags & TypeFlags.Union) { + if (awaitedTypeStack.lastIndexOf(type.id) >= 0) { + if (errorNode) { + error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method); } + return undefined; } - } - checkTypeReferenceOrImport(node); - } + const mapper = errorNode ? (constituentType: Type) => getAwaitedTypeNoAlias(constituentType, errorNode, diagnosticMessage, ...args) : getAwaitedTypeNoAlias; - 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); + awaitedTypeStack.push(type.id); + const mapped = mapType(type, mapper); + awaitedTypeStack.pop(); + + return typeAsAwaitable.awaitedTypeOfType = mapped; } - 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); + + // If `type` is generic and should be wrapped in `Awaited`, return it. + if (isAwaitedTypeNeeded(type)) { + return typeAsAwaitable.awaitedTypeOfType = type; } - checkSourceElement(node.type); - getTypeFromTypeNode(node); - } - function isPrivateWithinAmbient(node: Node): boolean { - return (hasEffectiveModifier(node, ModifierFlags.Private) || isPrivateIdentifierClassElementDeclaration(node)) && !!(node.flags & NodeFlags.Ambient); - } + const thisTypeForErrorOut: { value: Type | undefined } = { value: undefined }; + const promisedType = getPromisedTypeOfPromise(type, /*errorNode*/ undefined, thisTypeForErrorOut); + 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; + } - function getEffectiveDeclarationFlags(n: Declaration, flagsToCheck: ModifierFlags): ModifierFlags { - let flags = getCombinedModifierFlags(n); + // 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 = getAwaitedTypeNoAlias(promisedType, errorNode, diagnosticMessage, ...args); + awaitedTypeStack.pop(); - // 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; + if (!awaitedType) { + return undefined; } - flags |= ModifierFlags.Ambient; + + return typeAsAwaitable.awaitedTypeOfType = awaitedType; } - return flags & flagsToCheck; - } + // 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) { + Debug.assertIsDefined(diagnosticMessage); + let chain: DiagnosticMessageChain | undefined; + if (thisTypeForErrorOut.value) { + chain = chainDiagnosticMessages(chain, Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1, typeToString(type), typeToString(thisTypeForErrorOut.value)); + } + chain = chainDiagnosticMessages(chain, diagnosticMessage, ...args); + diagnostics.add(createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(errorNode), errorNode, chain)); + } + return undefined; + } - function checkFunctionOrConstructorSymbol(symbol: Symbol): void { - addLazyDiagnostic(() => checkFunctionOrConstructorSymbolWorker(symbol)); + return typeAsAwaitable.awaitedTypeOfType = type; } - function checkFunctionOrConstructorSymbolWorker(symbol: Symbol): void { - 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]; - } + /** + * 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); - 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); - } - }); + if (languageVersion >= ScriptTarget.ES2015) { + if (isErrorType(returnType)) { + return; } - } - - 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 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(getAwaitedTypeNoAlias(returnType) || voidType)); + return; } } + else { + // Always mark the type node as referenced if it points to a value + markTypeNodeAsReferenced(returnTypeNode); - 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; + if (isErrorType(returnType)) { + return; + } - function reportImplementationExpectedError(node: SignatureDeclaration): void { - if (node.name && nodeIsMissing(node.name)) { + 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; } - let seen = false; - const subsequentNode = forEachChild(node.parent, c => { - if (seen) { - return c; + const promiseConstructorSymbol = resolveEntityName(promiseConstructorName, SymbolFlags.Value, /*ignoreErrors*/ true); + const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : errorType; + if (isErrorType(promiseConstructorType)) { + 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 { - 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 as FunctionLikeDeclaration).name || subsequentNode; - const subsequentName = (subsequentNode as FunctionLikeDeclaration).name; - if (node.name && subsequentName && ( - // both are private identifiers - isPrivateIdentifier(node.name) && isPrivateIdentifier(subsequentName) && node.name.escapedText === subsequentName.escapedText || - // Both are computed property names - isComputedPropertyName(node.name) && isComputedPropertyName(subsequentName) && isTypeIdenticalTo(checkComputedPropertyName(node.name), checkComputedPropertyName(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) && - isStatic(node) !== isStatic(subsequentNode); - // 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 = isStatic(node) ? Diagnostics.Function_overload_must_be_static : Diagnostics.Function_overload_must_not_be_static; - error(errorNode, diagnostic); - } - return; - } - if (nodeIsPresent((subsequentNode as FunctionLikeDeclaration).body)) { - error(errorNode, Diagnostics.Function_implementation_name_must_be_0, declarationNameToString(node.name)); - return; - } + 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 errorNode: Node = node.name || node; - if (isConstructor) { - error(errorNode, Diagnostics.Constructor_implementation_is_missing); + + 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; } - 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); - } + + 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, /*withAlias*/ false, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); + } - let duplicateFunctionDeclaration = false; - let multipleConstructorImplementation = false; - let hasNonAmbientClass = false; - const functionDeclarations = [] as Declaration[]; - if (declarations) { - for (const current of declarations) { - const node = current as SignatureDeclaration | ClassDeclaration | ClassExpression; - 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; - } + /** 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; + } - if ((node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression) && !inAmbientContext) { - hasNonAmbientClass = true; - } + // if we fail to get a signature and return type here, we will have already reported a grammar error in `checkDecorators`. + const decoratorSignature = getDecoratorCallSignature(node); + if (!decoratorSignature?.resolvedReturnType) return; - 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); + let headMessage: DiagnosticMessage; + const expectedReturnType = decoratorSignature.resolvedReturnType; + switch (node.parent.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; + break; - if (bodyIsPresent && bodyDeclaration) { - if (isConstructor) { - multipleConstructorImplementation = true; - } - else { - duplicateFunctionDeclaration = true; - } - } - else if (previousDeclaration?.parent === node.parent && previousDeclaration.end !== node.pos) { - reportImplementationExpectedError(previousDeclaration); - } + case SyntaxKind.PropertyDeclaration: + if (!legacyDecorators) { + headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; + break; + } + // falls through - if (bodyIsPresent) { - if (!bodyDeclaration) { - bodyDeclaration = node as FunctionLikeDeclaration; - } - } - else { - hasOverloads = true; - } + case SyntaxKind.Parameter: + headMessage = Diagnostics.Decorator_function_return_type_is_0_but_is_expected_to_be_void_or_any; + break; - previousDeclaration = node; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; + break; - if (!inAmbientContextOrInterface) { - lastSeenNonAmbientDeclaration = node as FunctionLikeDeclaration; - } - } - if (isInJSFile(current) && isFunctionLike(current) && current.jsDoc) { - for (const node of current.jsDoc) { - if (node.tags) { - for (const tag of node.tags) { - if (isJSDocOverloadTag(tag)) { - hasOverloads = true; - } - } - } - } - } - } + default: + return Debug.failBadSyntaxKind(node.parent); } - if (multipleConstructorImplementation) { - forEach(functionDeclarations, declaration => { - error(declaration, Diagnostics.Multiple_constructor_implementations_are_not_allowed); - }); + checkTypeAssignableTo(returnType, expectedReturnType, node.expression, headMessage); + } + + /** + * Creates a synthetic `Signature` corresponding to a call signature. + */ + function createCallSignature( + typeParameters: readonly TypeParameter[] | undefined, + thisParameter: Symbol | undefined, + parameters: readonly Symbol[], + returnType: Type, + typePredicate?: TypePredicate, + minArgumentCount: number = parameters.length, + flags: SignatureFlags = SignatureFlags.None + ) { + const decl = factory.createFunctionTypeNode(/*typeParameters*/ undefined, emptyArray, factory.createKeywordTypeNode(SyntaxKind.AnyKeyword)); + return createSignature(decl, typeParameters, thisParameter, parameters, returnType, typePredicate, minArgumentCount, flags); + } + + /** + * Creates a synthetic `FunctionType` + */ + function createFunctionType( + typeParameters: readonly TypeParameter[] | undefined, + thisParameter: Symbol | undefined, + parameters: readonly Symbol[], + returnType: Type, + typePredicate?: TypePredicate, + minArgumentCount?: number, + flags?: SignatureFlags + ) { + const signature = createCallSignature(typeParameters, thisParameter, parameters, returnType, typePredicate, minArgumentCount, flags); + return getOrCreateTypeFromSignature(signature); + } + + function createGetterFunctionType(type: Type) { + return createFunctionType(/*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, type); + } + + function createSetterFunctionType(type: Type) { + const valueParam = createParameter("value" as __String, type); + return createFunctionType(/*typeParameters*/ undefined, /*thisParameter*/ undefined, [valueParam], voidType); + } + + /** + * 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), /*forDecoratorMetadata*/ false); + } + + function markEntityNameOrEntityExpressionAsReference(typeName: EntityNameOrEntityNameExpression | undefined, forDecoratorMetadata: boolean) { + if (!typeName) return; + + const rootName = getFirstIdentifier(typeName); + const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias; + const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); + if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) { + if (canCollectSymbolAliasAccessabilityData + && symbolIsValue(rootSymbol) + && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol)) + && !getTypeOnlyAliasDeclaration(rootSymbol)) { + markAliasSymbolAsReferenced(rootSymbol); + } + else if (forDecoratorMetadata + && getIsolatedModules(compilerOptions) + && getEmitModuleKind(compilerOptions) >= ModuleKind.ES2015 + && !symbolIsValue(rootSymbol) + && !some(rootSymbol.declarations, isTypeOnlyImportOrExportDeclaration)) { + const diag = error(typeName, Diagnostics.A_type_referenced_in_a_decorated_signature_must_be_imported_with_import_type_or_a_namespace_import_when_isolatedModules_and_emitDecoratorMetadata_are_enabled); + const aliasDeclaration = find(rootSymbol.declarations || emptyArray, isAliasSymbolDeclaration); + if (aliasDeclaration) { + addRelatedInfo(diag, createDiagnosticForNode(aliasDeclaration, Diagnostics._0_was_imported_here, idText(rootName))); + } + } } + } - if (duplicateFunctionDeclaration) { - forEach(functionDeclarations, declaration => { - error(getNameOfDeclaration(declaration) || declaration, Diagnostics.Duplicate_function_implementation); - }); + /** + * This function marks the type used for metadata decorator as referenced if it is import + * from external module. + * This is different from markTypeNodeAsReferenced because it tries to simplify type nodes in + * union and intersection type + * @param node + */ + function markDecoratorMedataDataTypeNodeAsReferenced(node: TypeNode | undefined): void { + const entityName = getEntityNameForDecoratorMetadata(node); + if (entityName && isEntityName(entityName)) { + markEntityNameOrEntityExpressionAsReference(entityName, /*forDecoratorMetadata*/ true); } + } - if (hasNonAmbientClass && !isConstructor && symbol.flags & SymbolFlags.Function && declarations) { - const relatedDiagnostics = filter(declarations, d => d.kind === SyntaxKind.ClassDeclaration) - .map(d => createDiagnosticForNode(d, Diagnostics.Consider_adding_a_declare_modifier_to_this_class)); + function getEntityNameForDecoratorMetadata(node: TypeNode | undefined): EntityName | undefined { + if (node) { + switch (node.kind) { + case SyntaxKind.IntersectionType: + case SyntaxKind.UnionType: + return getEntityNameForDecoratorMetadataFromTypeList((node as UnionOrIntersectionTypeNode).types); - forEach(declarations, declaration => { - const diagnostic = declaration.kind === SyntaxKind.ClassDeclaration - ? Diagnostics.Class_declaration_cannot_implement_overload_list_for_0 - : declaration.kind === SyntaxKind.FunctionDeclaration - ? Diagnostics.Function_with_bodies_can_only_merge_with_classes_that_are_ambient - : undefined; - if (diagnostic) { - addRelatedInfo( - error(getNameOfDeclaration(declaration) || declaration, diagnostic, symbolName(symbol)), - ...relatedDiagnostics - ); - } - }); - } + case SyntaxKind.ConditionalType: + return getEntityNameForDecoratorMetadataFromTypeList([(node as ConditionalTypeNode).trueType, (node as ConditionalTypeNode).falseType]); - // 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); + case SyntaxKind.ParenthesizedType: + case SyntaxKind.NamedTupleMember: + return getEntityNameForDecoratorMetadata((node as ParenthesizedTypeNode).type); + + case SyntaxKind.TypeReference: + return (node as TypeReferenceNode).typeName; + } } + } - if (hasOverloads) { - if (declarations) { - checkFlagAgreementBetweenOverloads(declarations, bodyDeclaration, flagsToCheck, someNodeFlags, allNodeFlags); - checkQuestionTokenAgreementBetweenOverloads(declarations, bodyDeclaration, someHaveQuestionToken, allHaveQuestionToken); + 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 (bodyDeclaration) { - const signatures = getSignaturesOfSymbol(symbol); - const bodySignature = getSignatureFromDeclaration(bodyDeclaration); - for (const signature of signatures) { - if (!isImplementationCompatibleWithOverload(bodySignature, signature)) { - const errorNode = signature.declaration && isJSDocSignature(signature.declaration) - ? (signature.declaration.parent as JSDocOverloadTag | JSDocCallbackTag).tagName - : signature.declaration; - addRelatedInfo( - error(errorNode, Diagnostics.This_overload_signature_is_not_compatible_with_its_implementation_signature), - createDiagnosticForNode(bodyDeclaration, Diagnostics.The_implementation_signature_is_declared_here) - ); - break; - } + 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 checkExportsOnMergedDeclarations(node: Declaration): void { - addLazyDiagnostic(() => checkExportsOnMergedDeclarationsWorker(node)); + function getParameterTypeNodeForDecoratorCheck(node: ParameterDeclaration): TypeNode | undefined { + const typeNode = getEffectiveTypeAnnotationNode(node); + return isRestParameter(node) ? getRestParameterElementType(typeNode) : typeNode; } - function checkExportsOnMergedDeclarationsWorker(node: Declaration): void { - // 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 = getSymbolOfDeclaration(node)!; - if (!symbol.exportSymbol) { - // this is a pure local symbol (all declarations are non-exported) - no need to check anything - return; - } + /** Check the decorators of a node */ + function checkDecorators(node: Node): void { + // skip this check for nodes that cannot have decorators. These should have already had an error reported by + // checkGrammarModifiers. + if (!canHaveDecorators(node) || !hasDecorators(node) || !node.modifiers || !nodeCanBeDecorated(legacyDecorators, node, node.parent, node.parent.parent)) { + return; } - // run the check only for the first declaration in the list - if (getDeclarationOfKind(symbol, node.kind) !== node) { + const firstDecorator = find(node.modifiers, isDecorator); + if (!firstDecorator) { 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; + if (legacyDecorators) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Decorate); + if (node.kind === SyntaxKind.Parameter) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Param); + } + } + else if (languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.ESDecorateAndRunInitializers); + if (isClassDeclaration(node)) { + if (!node.name) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.SetFunctionName); } else { - exportedDeclarationSpaces |= declarationSpaces; + const member = getFirstTransformableStaticClassElement(node); + if (member) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.SetFunctionName); + } } } - 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 (!isClassExpression(node)) { + if (isPrivateIdentifier(node.name) && (isMethodDeclaration(node) || isAccessor(node) || isAutoAccessorPropertyDeclaration(node))) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.SetFunctionName); } - else if (declarationSpaces & commonDeclarationSpacesForExportsAndLocals) { - error(name, Diagnostics.Individual_declarations_in_merged_declaration_0_must_be_all_exported_or_all_local, declarationNameToString(name)); + if (isComputedPropertyName(node.name)) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.PropKey); } } } - function getDeclarationSpaces(decl: Declaration): DeclarationSpaces { - let d = decl as Node; - switch (d.kind) { - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: + if (compilerOptions.emitDecoratorMetadata) { + checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata); - // 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; + // 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: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.EnumMember: - return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue; - case SyntaxKind.SourceFile: - return DeclarationSpaces.ExportType | DeclarationSpaces.ExportValue | DeclarationSpaces.ExportNamespace; - case SyntaxKind.ExportAssignment: - case SyntaxKind.BinaryExpression: - const node = d as ExportAssignment | BinaryExpression; - const expression = isExportAssignment(node) ? node.expression : node.right; - // Export assigned entity name expressions act as aliases and should fall through, otherwise they export values - if (!isEntityNameExpression(expression)) { - return DeclarationSpaces.ExportValue; + const constructor = getFirstConstructorWithBody(node); + if (constructor) { + for (const parameter of constructor.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + } } - d = 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(getSymbolOfDeclaration(d as ImportEqualsDeclaration | NamespaceImport | ImportClause | ExportAssignment | BinaryExpression)!); - 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; - case SyntaxKind.MethodSignature: - case SyntaxKind.PropertySignature: - return DeclarationSpaces.ExportType; - default: - return Debug.failBadSyntaxKind(d); - } - } - } + break; - function getAwaitedTypeOfPromise(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, ...args: DiagnosticArguments): Type | undefined { - const promisedType = getPromisedTypeOfPromise(type, errorNode); - return promisedType && getAwaitedType(promisedType, errorNode, diagnosticMessage, ...args); - } + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; + const otherAccessor = getDeclarationOfKind(getSymbolOfDeclaration(node), otherKind); + markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor)); + break; + case SyntaxKind.MethodDeclaration: + for (const parameter of node.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + } - /** - * 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, thisTypeForErrorOut?: { value?: Type }): Type | undefined { - // - // { // type - // then( // thenFunction - // onfulfilled: ( // onfulfilledParameterType - // value: T // valueParameterType - // ) => any - // ): any; - // } - // + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(node)); + break; - if (isTypeAny(type)) { - return undefined; - } + case SyntaxKind.PropertyDeclaration: + markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode(node)); + break; - const typeAsPromise = type as PromiseOrAwaitableType; - if (typeAsPromise.promisedTypeOfPromise) { - return typeAsPromise.promisedTypeOfPromise; + case SyntaxKind.Parameter: + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(node)); + const containingSignature = node.parent; + for (const parameter of containingSignature.parameters) { + markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + } + break; + } } - if (isReferenceToType(type, getGlobalPromiseType(/*reportErrors*/ false))) { - return typeAsPromise.promisedTypeOfPromise = getTypeArguments(type as GenericType)[0]; + for (const modifier of node.modifiers) { + if (isDecorator(modifier)) { + checkDecorator(modifier); + } } + } - // primitives with a `{ then() }` won't be unwrapped/adopted. - if (allTypesAssignableToKind(getBaseConstraintOrType(type), TypeFlags.Primitive | TypeFlags.Never)) { - return undefined; + function checkTailRecFunction(node: FunctionDeclaration): void { + if (node.body && node.name) { + const funcNameSymbol = getSymbolAtLocation(node.name) + if (funcNameSymbol) { + checkTailRecFunctionParameters(node.parameters); + const paramSymbols = flatMap(node.parameters, (param) => getSymbolsOfBindingName(param.name)); + forEach(node.body.statements, (s) => { + visitNode(s, checkTailRecFunctionVisitor(funcNameSymbol, paramSymbols)); + }); + } } - - const thenFunction = getTypeOfPropertyOfType(type, "then" as __String)!; // TODO: GH#18217 - if (isTypeAny(thenFunction)) { - return undefined; + else { + error(node, Diagnostics.Invalid_declaration_annotated_as_tailRec); } - - const thenSignatures = thenFunction ? getSignaturesOfType(thenFunction, SignatureKind.Call) : emptyArray; - if (thenSignatures.length === 0) { - if (errorNode) { - error(errorNode, Diagnostics.A_promise_must_have_a_then_method); + } + function checkTailRecVariableDeclaration(node: VariableDeclaration): void { + if (isIdentifier(node.name) && node.initializer && isArrowFunction(node.initializer)) { + const funcNameSymbol = getSymbolAtLocation(node.name) + if (funcNameSymbol) { + checkTailRecFunctionParameters(node.initializer.parameters); + const paramSymbols = flatMap(node.initializer.parameters, (param) => getSymbolsOfBindingName(param.name)); + if (isBlock(node.initializer.body)) { + forEach(node.initializer.body.statements, (s) => { + visitNode(s, checkTailRecFunctionVisitor(funcNameSymbol, paramSymbols)); + }); + } + else { + checkTailRecReturnStatement(funcNameSymbol, paramSymbols)(node.initializer.body); + } } - return undefined; } - - let thisTypeForError: Type | undefined; - let candidates: Signature[] | undefined; - for (const thenSignature of thenSignatures) { - const thisType = getThisTypeOfSignature(thenSignature); - if (thisType && thisType !== voidType && !isTypeRelatedTo(type, thisType, subtypeRelation)) { - thisTypeForError = thisType; + else { + error(node, Diagnostics.Invalid_declaration_annotated_as_tailRec); + } + } + function checkTailRecFunctionParameters(params: NodeArray): void { + forEach(params, (param) => { + if (!isIdentifier(param.name)) { + error(param, Diagnostics.Parameter_destructuring_is_not_allowed_in_a_tailRec_annotated_function); } - else { - candidates = append(candidates, thenSignature); + }); + } + function checkTailRecFunctionVisitor(funcNameSymbol: Symbol, parameterSymbols: readonly Symbol[]) { + return function (node: Node): VisitResult { + if (isReturnStatement(node) && node.expression) { + return visitEachChild( + node, + checkTailRecReturnStatement(funcNameSymbol, parameterSymbols), + nullTransformationContext + ); } - } - - if (!candidates) { - Debug.assertIsDefined(thisTypeForError); - if (thisTypeForErrorOut) { - thisTypeForErrorOut.value = thisTypeForError; + else if (isFunctionDeclaration(node) || isArrowFunction(node)) { + return visitEachChild( + node, + checkTailRecNestedFunction(funcNameSymbol), + nullTransformationContext + ); } - if (errorNode) { - error(errorNode, Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1, typeToString(type), typeToString(thisTypeForError)); + else if (isCallExpression(node)) { + const symbol = getSymbolAtLocation(node.expression); + if (symbol === funcNameSymbol) { + error(node, Diagnostics.A_recursive_call_must_be_in_a_tail_position_of_a_tailRec_annotated_function); + return node; + } } - return undefined; + return visitEachChild( + node, + checkTailRecFunctionVisitor(funcNameSymbol, parameterSymbols), + nullTransformationContext + ); + }; + } + function getSymbolsOfBindingName(node: BindingName): readonly Symbol[] { + if (isIdentifier(node)) { + const symbol = getSymbolAtLocation(node); + return symbol ? [symbol] : []; } - - const onfulfilledParameterType = getTypeWithFacts(getUnionType(map(candidates, getTypeOfFirstParameterOfSignature)), TypeFacts.NEUndefinedOrNull); - if (isTypeAny(onfulfilledParameterType)) { - return undefined; + else if (isObjectBindingPattern(node)) { + return flatMap(node.elements, (param) => getSymbolsOfBindingName(param.name)); } - - 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; + else { + return flatMap(node.elements, (param) => isBindingElement(param) ? getSymbolsOfBindingName(param.name) : undefined); } - - return typeAsPromise.promisedTypeOfPromise = getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature), UnionReduction.Subtype); } - /** - * Gets the "awaited type" of a type. - * @param type The type to await. - * @param withAlias When `true`, wraps the "awaited type" in `Awaited` if needed. - * @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, withAlias: boolean, errorNode: Node, diagnosticMessage: DiagnosticMessage, ...args: DiagnosticArguments): Type { - const awaitedType = withAlias ? - getAwaitedType(type, errorNode, diagnosticMessage, ...args) : - getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, ...args); - return awaitedType || errorType; + function checkTailRecNestedFunction(funcNameSymbol: Symbol) { + return function (node: Node): VisitResult { + if (isCallExpression(node) && isIdentifier(node.expression)) { + const callNameSymbol = getSymbolAtLocation(node.expression); + if (callNameSymbol === funcNameSymbol) { + error(node, Diagnostics.A_recursive_call_cannot_cross_a_function_boundary_in_a_tailRec_annotated_function); + return node; + } + } + return visitEachChild(node, checkTailRecNestedFunction(funcNameSymbol), nullTransformationContext); + }; } - /** - * Determines whether a type is an object with a callable `then` member. - */ - function isThenableType(type: Type): boolean { - if (allTypesAssignableToKind(getBaseConstraintOrType(type), TypeFlags.Primitive | TypeFlags.Never)) { - // primitive types cannot be considered "thenable" since they are not objects. - return false; - } - - const thenFunction = getTypeOfPropertyOfType(type, "then" as __String); - return !!thenFunction && getSignaturesOfType(getTypeWithFacts(thenFunction, TypeFacts.NEUndefinedOrNull), SignatureKind.Call).length > 0; + // TODO(peter): Right now the heuristic for detecting whether a recursive call is in the tail position + // is overly simple. It does not, for example, allow for recursive calls in ternery expressions. + function checkTailRecReturnStatement(funcNameSymbol: Symbol, parameterSymbols: readonly Symbol[], firstIter = true) { + return function (node: Node): VisitResult { + if (isCallExpression(node) && !firstIter) { + const symbol = getSymbolAtLocation(node.expression); + if (symbol === funcNameSymbol) { + error(node, Diagnostics.A_recursive_call_must_be_in_a_tail_position_of_a_tailRec_annotated_function); + } + } + return visitEachChild(node, checkTailRecReturnStatement(funcNameSymbol, parameterSymbols, false), nullTransformationContext); + }; } - interface AwaitedTypeInstantiation extends Type { - _awaitedTypeBrand: never; - aliasSymbol: Symbol; - aliasTypeArguments: readonly Type[]; - } + function checkFunctionDeclaration(node: FunctionDeclaration): void { + addLazyDiagnostic(checkFunctionDeclarationDiagnostics); - function isAwaitedTypeInstantiation(type: Type): type is AwaitedTypeInstantiation { - if (type.flags & TypeFlags.Conditional) { - const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ false); - return !!awaitedSymbol && type.aliasSymbol === awaitedSymbol && type.aliasTypeArguments?.length === 1; + function checkFunctionDeclarationDiagnostics() { + if (isTailRec(node)) { + checkTailRecFunction(node); + } + checkFunctionOrMethodDeclaration(node); + checkGrammarForGenerator(node); + checkCollisionsForDeclarationName(node, node.name); + const links = getNodeLinks(node); + if (links.tsPlusPipeableExtension) { + checkFluentPipeableAgreement(links.tsPlusPipeableExtension); + } } - return false; } - /** - * For a generic `Awaited`, gets `T`. - */ - function unwrapAwaitedType(type: Type) { - return type.flags & TypeFlags.Union ? mapType(type, unwrapAwaitedType) : - isAwaitedTypeInstantiation(type) ? type.aliasTypeArguments[0] : - type; - } - - function isAwaitedTypeNeeded(type: Type) { - // If this is already an `Awaited`, we shouldn't wrap it. This helps to avoid `Awaited>` in higher-order. - if (isTypeAny(type) || isAwaitedTypeInstantiation(type)) { - return false; + 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); } - // We only need `Awaited` if `T` contains possibly non-primitive types. - if (isGenericObjectType(type)) { - const baseConstraint = getBaseConstraintOfType(type); - // We only need `Awaited` if `T` is a type variable that has no base constraint, or the base constraint of `T` is `any`, `unknown`, `{}`, `object`, - // or is promise-like. - if (baseConstraint ? - baseConstraint.flags & TypeFlags.AnyOrUnknown || isEmptyObjectType(baseConstraint) || someType(baseConstraint, isThenableType) : - maybeTypeOfKind(type, TypeFlags.TypeVariable)) { - return true; - } + if (node.name) { + checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); } - - return false; + checkSourceElement(node.typeExpression); + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); } - function tryCreateAwaitedType(type: Type): Type | undefined { - // Nothing to do if `Awaited` doesn't exist - const awaitedSymbol = getGlobalAwaitedSymbol(/*reportErrors*/ true); - if (awaitedSymbol) { - // Unwrap unions that may contain `Awaited`, otherwise its possible to manufacture an `Awaited | U>` where - // an `Awaited` would suffice. - return getTypeAliasInstantiation(awaitedSymbol, [unwrapAwaitedType(type)]); + function checkJSDocTemplateTag(node: JSDocTemplateTag): void { + checkSourceElement(node.constraint); + for (const tp of node.typeParameters) { + checkSourceElement(tp); } - - return undefined; } - function createAwaitedTypeIfNeeded(type: Type): Type { - // We wrap type `T` in `Awaited` based on the following conditions: - // - `T` is not already an `Awaited`, and - // - `T` is generic, and - // - One of the following applies: - // - `T` has no base constraint, or - // - The base constraint of `T` is `any`, `unknown`, `object`, or `{}`, or - // - The base constraint of `T` is an object type with a callable `then` method. + function checkJSDocTypeTag(node: JSDocTypeTag) { + checkSourceElement(node.typeExpression); + } - if (isAwaitedTypeNeeded(type)) { - const awaitedType = tryCreateAwaitedType(type); - if (awaitedType) { - return awaitedType; + function checkJSDocSatisfiesTag(node: JSDocSatisfiesTag) { + checkSourceElement(node.typeExpression); + const host = getEffectiveJSDocHost(node); + if (host) { + const tags = getAllJSDocTags(host, isJSDocSatisfiesTag); + if (length(tags) > 1) { + for (let i = 1; i < length(tags); i++) { + const tagName = tags[i].tagName; + error(tagName, Diagnostics._0_tag_already_specified, idText(tagName)); + } } } + } - Debug.assert(isAwaitedTypeInstantiation(type) || getPromisedTypeOfPromise(type) === undefined, "type provided should not be a non-generic 'promise'-like."); - return type; + function checkJSDocLinkLikeTag(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain) { + if (node.name) { + resolveJSDocMemberName(node.name, /*ignoreErrors*/ true); + } } - /** - * 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, ...args: DiagnosticArguments): Type | undefined { - const awaitedType = getAwaitedTypeNoAlias(type, errorNode, diagnosticMessage, ...args); - return awaitedType && createAwaitedTypeIfNeeded(awaitedType); + function checkJSDocParameterTag(node: JSDocParameterTag) { + checkSourceElement(node.typeExpression); + } + function checkJSDocPropertyTag(node: JSDocPropertyTag) { + checkSourceElement(node.typeExpression); } - /** - * Gets the "awaited type" of a type without introducing an `Awaited` wrapper. - * - * @see {@link getAwaitedType} - */ - function getAwaitedTypeNoAlias(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, ...args: DiagnosticArguments): Type | undefined { - if (isTypeAny(type)) { - return type; - } + function checkJSDocFunctionType(node: JSDocFunctionType): void { + addLazyDiagnostic(checkJSDocFunctionTypeImplicitAny); + checkSignatureDeclaration(node); - // If this is already an `Awaited`, just return it. This avoids `Awaited>` in higher-order - if (isAwaitedTypeInstantiation(type)) { - return type; + function checkJSDocFunctionTypeImplicitAny() { + if (!node.type && !isJSDocConstructSignature(node)) { + reportImplicitAny(node, anyType); + } } + } - // If we've already cached an awaited type, return a possible `Awaited` for it. - const typeAsAwaitable = type as PromiseOrAwaitableType; - if (typeAsAwaitable.awaitedTypeOfType) { - return typeAsAwaitable.awaitedTypeOfType; + 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)); } + } - // For a union, get a union of the awaited types of each constituent. - if (type.flags & TypeFlags.Union) { - if (awaitedTypeStack.lastIndexOf(type.id) >= 0) { - if (errorNode) { - error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method); - } - return undefined; - } - - const mapper = errorNode ? (constituentType: Type) => getAwaitedTypeNoAlias(constituentType, errorNode, diagnosticMessage, ...args) : getAwaitedTypeNoAlias; - - awaitedTypeStack.push(type.id); - const mapped = mapType(type, mapper); - awaitedTypeStack.pop(); - - return typeAsAwaitable.awaitedTypeOfType = mapped; + 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; } - // If `type` is generic and should be wrapped in `Awaited`, return it. - if (isAwaitedTypeNeeded(type)) { - return typeAsAwaitable.awaitedTypeOfType = type; + 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 thisTypeForErrorOut: { value: Type | undefined } = { value: undefined }; - const promisedType = getPromisedTypeOfPromise(type, /*errorNode*/ undefined, thisTypeForErrorOut); - 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 = getAwaitedTypeNoAlias(promisedType, errorNode, diagnosticMessage, ...args); - awaitedTypeStack.pop(); - - if (!awaitedType) { - return undefined; + 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)); } - - 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) { - Debug.assertIsDefined(diagnosticMessage); - let chain: DiagnosticMessageChain | undefined; - if (thisTypeForErrorOut.value) { - chain = chainDiagnosticMessages(chain, Diagnostics.The_this_context_of_type_0_is_not_assignable_to_method_s_this_of_type_1, typeToString(type), typeToString(thisTypeForErrorOut.value)); - } - chain = chainDiagnosticMessages(chain, diagnosticMessage, ...args); - diagnostics.add(createDiagnosticForNodeFromMessageChain(getSourceFileOfNode(errorNode), errorNode, chain)); - } - return undefined; + function checkJSDocAccessibilityModifiers(node: JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag): void { + const host = getJSDocHost(node); + if (host && isPrivateIdentifierClassElementDeclaration(host)) { + error(node, Diagnostics.An_accessibility_modifier_cannot_be_used_with_a_private_identifier); } - - 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 (isErrorType(returnType)) { - 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(getAwaitedTypeNoAlias(returnType) || voidType)); - return; - } + 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; } - else { - // Always mark the type node as referenced if it points to a value - markTypeNodeAsReferenced(returnTypeNode); + } - if (isErrorType(returnType)) { - return; - } + function checkFunctionOrMethodDeclaration(node: FunctionDeclaration | MethodDeclaration | MethodSignature): void { + checkDecorators(node); + checkSignatureDeclaration(node); + const functionFlags = getFunctionFlags(node); - 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; - } + // 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); + } - const promiseConstructorSymbol = resolveEntityName(promiseConstructorName, SymbolFlags.Value, /*ignoreErrors*/ true); - const promiseConstructorType = promiseConstructorSymbol ? getTypeOfSymbol(promiseConstructorSymbol) : errorType; - if (isErrorType(promiseConstructorType)) { - 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; - } + if (hasBindableName(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 = getSymbolOfDeclaration(node); + const localSymbol = node.localSymbol || symbol; - 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; - } + // 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 = localSymbol.declarations?.find( + // Get first non javascript function declaration + declaration => declaration.kind === node.kind && !(declaration.flags & NodeFlags.JavaScriptFile)); - 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; + // Only type check the symbol once + if (node === firstDeclaration) { + checkFunctionOrConstructorSymbol(localSymbol); } - // 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; + if (symbol.parent) { + // run check on export symbol to check that modifiers agree across all exported declarations + checkFunctionOrConstructorSymbol(symbol); } } - checkAwaitedType(returnType, /*withAlias*/ false, 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; - } + const body = node.kind === SyntaxKind.MethodSignature ? undefined : node.body; + checkSourceElement(body); + checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, getReturnTypeFromAnnotation(node)); - // if we fail to get a signature and return type here, we will have already reported a grammar error in `checkDecorators`. - const decoratorSignature = getDecoratorCallSignature(node); - if (!decoratorSignature?.resolvedReturnType) return; + addLazyDiagnostic(checkFunctionOrMethodDeclarationDiagnostics); - let headMessage: DiagnosticMessage; - const expectedReturnType = decoratorSignature.resolvedReturnType; - switch (node.parent.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; - break; + // 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.typeExpression.type, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature); + } + } - case SyntaxKind.PropertyDeclaration: - if (!legacyDecorators) { - headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; - break; + function checkFunctionOrMethodDeclarationDiagnostics() { + if (!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); } - // falls through - - case SyntaxKind.Parameter: - headMessage = Diagnostics.Decorator_function_return_type_is_0_but_is_expected_to_be_void_or_any; - break; - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - headMessage = Diagnostics.Decorator_function_return_type_0_is_not_assignable_to_type_1; - break; - default: - return Debug.failBadSyntaxKind(node.parent); + 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)); + } + } } - - checkTypeAssignableTo(returnType, expectedReturnType, node.expression, headMessage); } - /** - * Creates a synthetic `Signature` corresponding to a call signature. - */ - function createCallSignature( - typeParameters: readonly TypeParameter[] | undefined, - thisParameter: Symbol | undefined, - parameters: readonly Symbol[], - returnType: Type, - typePredicate?: TypePredicate, - minArgumentCount: number = parameters.length, - flags: SignatureFlags = SignatureFlags.None - ) { - const decl = factory.createFunctionTypeNode(/*typeParameters*/ undefined, emptyArray, factory.createKeywordTypeNode(SyntaxKind.AnyKeyword)); - return createSignature(decl, typeParameters, thisParameter, parameters, returnType, typePredicate, minArgumentCount, flags); - } + function registerForUnusedIdentifiersCheck(node: PotentiallyUnusedIdentifier): void { + addLazyDiagnostic(registerForUnusedIdentifiersCheckDiagnostics); - /** - * Creates a synthetic `FunctionType` - */ - function createFunctionType( - typeParameters: readonly TypeParameter[] | undefined, - thisParameter: Symbol | undefined, - parameters: readonly Symbol[], - returnType: Type, - typePredicate?: TypePredicate, - minArgumentCount?: number, - flags?: SignatureFlags - ) { - const signature = createCallSignature(typeParameters, thisParameter, parameters, returnType, typePredicate, minArgumentCount, flags); - return getOrCreateTypeFromSignature(signature); + function registerForUnusedIdentifiersCheckDiagnostics() { + // May be in a call such as getTypeOfNode that happened to call this. But potentiallyUnusedIdentifiers is only defined in the scope of `checkSourceFile`. + 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); + } } - function createGetterFunctionType(type: Type) { - return createFunctionType(/*typeParameters*/ undefined, /*thisParameter*/ undefined, emptyArray, type); + 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 createSetterFunctionType(type: Type) { - const valueParam = createParameter("value" as __String, type); - return createFunctionType(/*typeParameters*/ undefined, /*thisParameter*/ undefined, [valueParam], voidType); + 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)); } - /** - * 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), /*forDecoratorMetadata*/ false); + function isIdentifierThatStartsWithUnderscore(node: Node) { + return isIdentifier(node) && idText(node).charCodeAt(0) === CharacterCodes._; } - function markEntityNameOrEntityExpressionAsReference(typeName: EntityNameOrEntityNameExpression | undefined, forDecoratorMetadata: boolean) { - if (!typeName) return; - - const rootName = getFirstIdentifier(typeName); - const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias; - const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); - if (rootSymbol && rootSymbol.flags & SymbolFlags.Alias) { - if (canCollectSymbolAliasAccessabilityData - && symbolIsValue(rootSymbol) - && !isConstEnumOrConstEnumOnlyModule(resolveAlias(rootSymbol)) - && !getTypeOnlyAliasDeclaration(rootSymbol)) { - markAliasSymbolAsReferenced(rootSymbol); - } - else if (forDecoratorMetadata - && getIsolatedModules(compilerOptions) - && getEmitModuleKind(compilerOptions) >= ModuleKind.ES2015 - && !symbolIsValue(rootSymbol) - && !some(rootSymbol.declarations, isTypeOnlyImportOrExportDeclaration)) { - const diag = error(typeName, Diagnostics.A_type_referenced_in_a_decorated_signature_must_be_imported_with_import_type_or_a_namespace_import_when_isolatedModules_and_emitDecoratorMetadata_are_enabled); - const aliasDeclaration = find(rootSymbol.declarations || emptyArray, isAliasSymbolDeclaration); - if (aliasDeclaration) { - addRelatedInfo(diag, createDiagnosticForNode(aliasDeclaration, Diagnostics._0_was_imported_here, idText(rootName))); - } + 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 = getSymbolOfDeclaration(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 as ConstructorDeclaration).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: + case SyntaxKind.ClassStaticBlockDeclaration: + // Can't be private + break; + default: + Debug.fail("Unexpected class member"); } } } - /** - * This function marks the type used for metadata decorator as referenced if it is import - * from external module. - * This is different from markTypeNodeAsReferenced because it tries to simplify type nodes in - * union and intersection type - * @param node - */ - function markDecoratorMedataDataTypeNodeAsReferenced(node: TypeNode | undefined): void { - const entityName = getEntityNameForDecoratorMetadata(node); - if (entityName && isEntityName(entityName)) { - markEntityNameOrEntityExpressionAsReference(entityName, /*forDecoratorMetadata*/ true); + function 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 getEntityNameForDecoratorMetadata(node: TypeNode | undefined): EntityName | undefined { - if (node) { - switch (node.kind) { - case SyntaxKind.IntersectionType: - case SyntaxKind.UnionType: - return getEntityNameForDecoratorMetadataFromTypeList((node as UnionOrIntersectionTypeNode).types); - - case SyntaxKind.ConditionalType: - return getEntityNameForDecoratorMetadataFromTypeList([(node as ConditionalTypeNode).trueType, (node as ConditionalTypeNode).falseType]); - - case SyntaxKind.ParenthesizedType: - case SyntaxKind.NamedTupleMember: - return getEntityNameForDecoratorMetadata((node as ParenthesizedTypeNode).type); + 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. + const declarations = getSymbolOfDeclaration(node).declarations; + if (!declarations || last(declarations) !== node) return; - case SyntaxKind.TypeReference: - return (node as TypeReferenceNode).typeName; - } - } - } + const typeParameters = getEffectiveTypeParameterDeclarations(node); + const seenParentsWithEveryUnused = new Set(); - 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; - } + for (const typeParameter of typeParameters) { + if (!isTypeParameterUnused(typeParameter)) continue; - 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; + const name = idText(typeParameter.name); + const { parent } = typeParameter; + if (parent.kind !== SyntaxKind.InferType && parent.typeParameters!.every(isTypeParameterUnused)) { + if (tryAddToSet(seenParentsWithEveryUnused, parent)) { + const sourceFile = getSourceFileOfNode(parent); + const range = isJSDocTemplateTag(parent) + // Whole @template tag + ? rangeOfNode(parent) + // Include the `<>` in the error message + : rangeOfTypeParameters(sourceFile, parent.typeParameters!); + const only = parent.typeParameters!.length === 1; + //TODO: following line is possible reason for bug #41974, unusedTypeParameters_TemplateTag + const messageAndArg: DiagnosticAndArguments = only + ? [Diagnostics._0_is_declared_but_its_value_is_never_read, name] + : [Diagnostics.All_type_parameters_are_unused]; + addDiagnostic(typeParameter, UnusedKind.Parameter, createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, ...messageAndArg)); } } else { - commonEntityName = individualEntityName; + //TODO: following line is possible reason for bug #41974, unusedTypeParameters_TemplateTag + addDiagnostic(typeParameter, UnusedKind.Parameter, createDiagnosticForNode(typeParameter, Diagnostics._0_is_declared_but_its_value_is_never_read, name)); } } - return commonEntityName; } - - function getParameterTypeNodeForDecoratorCheck(node: ParameterDeclaration): TypeNode | undefined { - const typeNode = getEffectiveTypeAnnotationNode(node); - return isRestParameter(node) ? getRestParameterElementType(typeNode) : typeNode; + function isTypeParameterUnused(typeParameter: TypeParameterDeclaration): boolean { + return !(getMergedSymbol(typeParameter.symbol).isReferenced! & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderscore(typeParameter.name); } - /** Check the decorators of a node */ - function checkDecorators(node: Node): void { - // skip this check for nodes that cannot have decorators. These should have already had an error reported by - // checkGrammarModifiers. - if (!canHaveDecorators(node) || !hasDecorators(node) || !node.modifiers || !nodeCanBeDecorated(legacyDecorators, node, node.parent, node.parent.parent)) { - return; + function addToGroup(map: Map, 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); } - - const firstDecorator = find(node.modifiers, isDecorator); - if (!firstDecorator) { - return; + else { + map.set(keyString, [key, [value]]); } + } - if (legacyDecorators) { - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Decorate); - if (node.kind === SyntaxKind.Parameter) { - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Param); + function tryGetRootParameterDeclaration(node: Node): ParameterDeclaration | undefined { + return tryCast(getRootDeclaration(node), isParameter); + } + + function isValidUnusedLocalDeclaration(declaration: Declaration): boolean { + if (isBindingElement(declaration)) { + if (isObjectBindingPattern(declaration.parent)) { + /** + * ignore starts with underscore names _ + * const { a: _a } = { a: 1 } + */ + return !!(declaration.propertyName && isIdentifierThatStartsWithUnderscore(declaration.name)); } + return isIdentifierThatStartsWithUnderscore(declaration.name); } - else if (languageVersion < ScriptTarget.ESNext) { - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.ESDecorateAndRunInitializers); - if (isClassDeclaration(node)) { - if (!node.name) { - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.SetFunctionName); - } - else { - const member = getFirstTransformableStaticClassElement(node); - if (member) { - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.SetFunctionName); - } - } - } - else if (!isClassExpression(node)) { - if (isPrivateIdentifier(node.name) && (isMethodDeclaration(node) || isAccessor(node) || isAutoAccessorPropertyDeclaration(node))) { - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.SetFunctionName); - } - if (isComputedPropertyName(node.name)) { - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.PropKey); - } + return isAmbientModule(declaration) || + (isVariableDeclaration(declaration) && isForInOrOfStatement(declaration.parent.parent) || isImportedDeclaration(declaration)) && isIdentifierThatStartsWithUnderscore(declaration.name!); + } + + function checkUnusedLocalsAndParameters(nodeWithLocals: HasLocals, 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; } - } - if (compilerOptions.emitDecoratorMetadata) { - checkExternalEmitHelpers(firstDecorator, ExternalEmitHelpers.Metadata); + if (local.declarations) { + for (const declaration of local.declarations) { + if (isValidUnusedLocalDeclaration(declaration)) { + continue; + } - // 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)); + if (isImportedDeclaration(declaration)) { + const importClause = importClauseFromImported(declaration); + if (!importClause.parent.isTsPlusGlobal) { + addToGroup(unusedImports, importClause, declaration, getNodeId); } } - break; - - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - const otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; - const otherAccessor = getDeclarationOfKind(getSymbolOfDeclaration(node), otherKind); - markDecoratorMedataDataTypeNodeAsReferenced(getAnnotatedAccessorTypeNode(node) || otherAccessor && getAnnotatedAccessorTypeNode(otherAccessor)); - break; - case SyntaxKind.MethodDeclaration: - for (const parameter of node.parameters) { - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + 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); + } } - - markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveReturnTypeNode(node)); - break; - - case SyntaxKind.PropertyDeclaration: - markDecoratorMedataDataTypeNodeAsReferenced(getEffectiveTypeAnnotationNode(node)); - break; - - case SyntaxKind.Parameter: - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(node)); - const containingSignature = node.parent; - for (const parameter of containingSignature.parameters) { - markDecoratorMedataDataTypeNodeAsReferenced(getParameterTypeNodeForDecoratorCheck(parameter)); + else if (isVariableDeclaration(declaration)) { + addToGroup(unusedVariables, declaration.parent, declaration, getNodeId); } - break; + 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)) { + if (isBindingElement(declaration) && isArrayBindingPattern(declaration.parent)) { + addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); + } + else { + addDiagnostic(parameter, UnusedKind.Parameter, createDiagnosticForNode(name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(local))); + } + } + } + else { + errorUnusedLocal(declaration, symbolName(local), addDiagnostic); + } + } + } } - } - - for (const modifier of node.modifiers) { - if (isDecorator(modifier)) { - checkDecorator(modifier); + }); + 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 checkTailRecFunction(node: FunctionDeclaration): void { - if (node.body && node.name) { - const funcNameSymbol = getSymbolAtLocation(node.name) - if (funcNameSymbol) { - checkTailRecFunctionParameters(node.parameters); - const paramSymbols = flatMap(node.parameters, (param) => getSymbolsOfBindingName(param.name)); - forEach(node.body.statements, (s) => { - visitNode(s, checkTailRecFunctionVisitor(funcNameSymbol, paramSymbols)); - }); + function checkPotentialUncheckedRenamedBindingElementsInTypes() { + for (const node of potentialUnusedRenamedBindingElementsInTypes) { + if (!getSymbolOfDeclaration(node)?.isReferenced) { + const wrappingDeclaration = walkUpBindingElementsAndPatterns(node); + Debug.assert(isParameterDeclaration(wrappingDeclaration), "Only parameter declaration should be checked here"); + const diagnostic = createDiagnosticForNode(node.name, Diagnostics._0_is_an_unused_renaming_of_1_Did_you_intend_to_use_it_as_a_type_annotation, declarationNameToString(node.name), declarationNameToString(node.propertyName)); + if (!wrappingDeclaration.type) { + // entire parameter does not have type annotation, suggest adding an annotation + addRelatedInfo( + diagnostic, + createFileDiagnostic(getSourceFileOfNode(wrappingDeclaration), wrappingDeclaration.end, 1, Diagnostics.We_can_only_write_a_type_for_0_by_adding_a_type_for_the_entire_parameter_here, declarationNameToString(node.propertyName)) + ); + } + diagnostics.add(diagnostic); } } - else { - error(node, Diagnostics.Invalid_declaration_annotated_as_tailRec); + } + + 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); } } - function checkTailRecVariableDeclaration(node: VariableDeclaration): void { - if (isIdentifier(node.name) && node.initializer && isArrowFunction(node.initializer)) { - const funcNameSymbol = getSymbolAtLocation(node.name) - if (funcNameSymbol) { - checkTailRecFunctionParameters(node.initializer.parameters); - const paramSymbols = flatMap(node.initializer.parameters, (param) => getSymbolsOfBindingName(param.name)); - if (isBlock(node.initializer.body)) { - forEach(node.initializer.body.statements, (s) => { - visitNode(s, checkTailRecFunctionVisitor(funcNameSymbol, paramSymbols)); - }); - } - else { - checkTailRecReturnStatement(funcNameSymbol, paramSymbols)(node.initializer.body); - } - } + + 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 { - error(node, Diagnostics.Invalid_declaration_annotated_as_tailRec); + forEach(node.statements, checkSourceElement); + } + if (node.locals) { + registerForUnusedIdentifiersCheck(node); } } - function checkTailRecFunctionParameters(params: NodeArray): void { - forEach(params, (param) => { - if (!isIdentifier(param.name)) { - error(param, Diagnostics.Parameter_destructuring_is_not_allowed_in_a_tailRec_annotated_function); + + 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 as FunctionLikeDeclaration).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 checkTailRecFunctionVisitor(funcNameSymbol: Symbol, parameterSymbols: readonly Symbol[]) { - return function (node: Node): VisitResult { - if (isReturnStatement(node) && node.expression) { - return visitEachChild( - node, - checkTailRecReturnStatement(funcNameSymbol, parameterSymbols), - nullTransformationContext - ); - } - else if (isFunctionDeclaration(node) || isArrowFunction(node)) { - return visitEachChild( - node, - checkTailRecNestedFunction(funcNameSymbol), - nullTransformationContext - ); - } - else if (isCallExpression(node)) { - const symbol = getSymbolAtLocation(node.expression); - if (symbol === funcNameSymbol) { - error(node, Diagnostics.A_recursive_call_must_be_in_a_tail_position_of_a_tailRec_annotated_function); - return node; - } - } - return visitEachChild( - node, - checkTailRecFunctionVisitor(funcNameSymbol, parameterSymbols), - nullTransformationContext - ); - }; - } - function getSymbolsOfBindingName(node: BindingName): readonly Symbol[] { - if (isIdentifier(node)) { - const symbol = getSymbolAtLocation(node); - return symbol ? [symbol] : []; + + /** + * Checks whether an {@link Identifier}, in the context of another {@link Node}, would collide with a runtime value + * of {@link name} in an outer scope. This is used to check for collisions for downlevel transformations that + * require names like `Object`, `Promise`, `Reflect`, `require`, `exports`, etc. + */ + function needCollisionCheckForIdentifier(node: Node, identifier: Identifier | undefined, name: string): boolean { + if (identifier?.escapedText !== name) { + return false; } - else if (isObjectBindingPattern(node)) { - return flatMap(node.elements, (param) => getSymbolsOfBindingName(param.name)); + + 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 || + node.kind === SyntaxKind.PropertyAssignment) { + // it is ok to have member named '_super', '_this', `Promise`, etc. - member access is always qualified + return false; + } + + if (node.flags & NodeFlags.Ambient) { + // ambient context - no codegen impact + return false; + } + + if (isImportClause(node) || isImportEqualsDeclaration(node) || isImportSpecifier(node)) { + // type-only imports do not require collision checks against runtime values. + if (isTypeOnlyImportOrExportDeclaration(node)) { + return false; + } } - else { - return flatMap(node.elements, (param) => isBindingElement(param) ? getSymbolsOfBindingName(param.name) : undefined); + + const root = getRootDeclaration(node); + if (isParameter(root) && nodeIsMissing((root.parent as FunctionLikeDeclaration).body)) { + // just an overload - no codegen impact + return false; } + + return true; } - function checkTailRecNestedFunction(funcNameSymbol: Symbol) { - return function (node: Node): VisitResult { - if (isCallExpression(node) && isIdentifier(node.expression)) { - const callNameSymbol = getSymbolAtLocation(node.expression); - if (callNameSymbol === funcNameSymbol) { - error(node, Diagnostics.A_recursive_call_cannot_cross_a_function_boundary_in_a_tailRec_annotated_function); - return node; + // 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 as Declaration), 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 visitEachChild(node, checkTailRecNestedFunction(funcNameSymbol), nullTransformationContext); - }; + return false; + }); } - // TODO(peter): Right now the heuristic for detecting whether a recursive call is in the tail position - // is overly simple. It does not, for example, allow for recursive calls in ternery expressions. - function checkTailRecReturnStatement(funcNameSymbol: Symbol, parameterSymbols: readonly Symbol[], firstIter = true) { - return function (node: Node): VisitResult { - if (isCallExpression(node) && !firstIter) { - const symbol = getSymbolAtLocation(node.expression); - if (symbol === funcNameSymbol) { - error(node, Diagnostics.A_recursive_call_must_be_in_a_tail_position_of_a_tailRec_annotated_function); + function checkIfNewTargetIsCapturedInEnclosingScope(node: Node): void { + findAncestor(node, current => { + if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureNewTarget) { + const isDeclaration = node.kind !== SyntaxKind.Identifier; + if (isDeclaration) { + error(getNameOfDeclaration(node as Declaration), 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 visitEachChild(node, checkTailRecReturnStatement(funcNameSymbol, parameterSymbols, false), nullTransformationContext); - }; + return false; + }); } - function checkFunctionDeclaration(node: FunctionDeclaration): void { - addLazyDiagnostic(checkFunctionDeclarationDiagnostics); + function checkCollisionWithRequireExportsInGeneratedCode(node: Node, name: Identifier | undefined) { + // No need to check for require or exports for ES6 modules and later + if (moduleKind >= ModuleKind.ES2015 && !(moduleKind >= ModuleKind.Node16 && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { + return; + } - function checkFunctionDeclarationDiagnostics() { - if (isTailRec(node)) { - checkTailRecFunction(node); - } - checkFunctionOrMethodDeclaration(node); - checkGrammarForGenerator(node); - checkCollisionsForDeclarationName(node, node.name); - const links = getNodeLinks(node); - if (links.tsPlusPipeableExtension) { - checkFluentPipeableAgreement(links.tsPlusPipeableExtension); - } + if (!name || !needCollisionCheckForIdentifier(node, name, "require") && !needCollisionCheckForIdentifier(node, name, "exports")) { + return; } - } - 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); + // Uninstantiated modules shouldnt do this check + if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { + return; } - if (node.name) { - checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0); + // 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 as SourceFile)) { + // 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)); } - checkSourceElement(node.typeExpression); - checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); } - function checkJSDocTemplateTag(node: JSDocTemplateTag): void { - checkSourceElement(node.constraint); - for (const tp of node.typeParameters) { - checkSourceElement(tp); + function checkCollisionWithGlobalPromiseInGeneratedCode(node: Node, name: Identifier | undefined): void { + if (!name || languageVersion >= ScriptTarget.ES2017 || !needCollisionCheckForIdentifier(node, name, "Promise")) { + return; } - } - function checkJSDocTypeTag(node: JSDocTypeTag) { - checkSourceElement(node.typeExpression); - } + // Uninstantiated modules shouldnt do this check + if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { + return; + } - function checkJSDocSatisfiesTag(node: JSDocSatisfiesTag) { - checkSourceElement(node.typeExpression); - const host = getEffectiveJSDocHost(node); - if (host) { - const tags = getAllJSDocTags(host, isJSDocSatisfiesTag); - if (length(tags) > 1) { - for (let i = 1; i < length(tags); i++) { - const tagName = tags[i].tagName; - error(tagName, Diagnostics._0_tag_already_specified, idText(tagName)); - } - } + // 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 as SourceFile) && 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 checkJSDocLinkLikeTag(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain) { - if (node.name) { - resolveJSDocMemberName(node.name, /*ignoreErrors*/ true); + function recordPotentialCollisionWithWeakMapSetInGeneratedCode(node: Node, name: Identifier): void { + if (languageVersion <= ScriptTarget.ES2021 + && (needCollisionCheckForIdentifier(node, name, "WeakMap") || needCollisionCheckForIdentifier(node, name, "WeakSet"))) { + potentialWeakMapSetCollisions.push(node); } } - function checkJSDocParameterTag(node: JSDocParameterTag) { - checkSourceElement(node.typeExpression); - } - function checkJSDocPropertyTag(node: JSDocPropertyTag) { - checkSourceElement(node.typeExpression); + function checkWeakMapSetCollision(node: Node) { + const enclosingBlockScope = getEnclosingBlockScopeContainer(node); + if (getNodeCheckFlags(enclosingBlockScope) & NodeCheckFlags.ContainsClassWithPrivateIdentifiers) { + Debug.assert(isNamedDeclaration(node) && isIdentifier(node.name) && typeof node.name.escapedText === "string", "The target of a WeakMap/WeakSet collision check should be an identifier"); + errorSkippedOn("noEmit", node, Diagnostics.Compiler_reserves_name_0_when_emitting_private_identifier_downlevel, node.name.escapedText); + } } - function checkJSDocFunctionType(node: JSDocFunctionType): void { - addLazyDiagnostic(checkJSDocFunctionTypeImplicitAny); - checkSignatureDeclaration(node); + function recordPotentialCollisionWithReflectInGeneratedCode(node: Node, name: Identifier | undefined): void { + if (name && languageVersion >= ScriptTarget.ES2015 && languageVersion <= ScriptTarget.ES2021 + && needCollisionCheckForIdentifier(node, name, "Reflect")) { + potentialReflectCollisions.push(node); + } + } - function checkJSDocFunctionTypeImplicitAny() { - if (!node.type && !isJSDocConstructSignature(node)) { - reportImplicitAny(node, anyType); + function checkReflectCollision(node: Node) { + let hasCollision = false; + if (isClassExpression(node)) { + // ClassExpression names don't contribute to their containers, but do matter for any of their block-scoped members. + for (const member of node.members) { + if (getNodeCheckFlags(member) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { + hasCollision = true; + break; + } + } + } + else if (isFunctionExpression(node)) { + // FunctionExpression names don't contribute to their containers, but do matter for their contents + if (getNodeCheckFlags(node) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { + hasCollision = true; + } + } + else { + const container = getEnclosingBlockScopeContainer(node); + if (container && getNodeCheckFlags(container) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { + hasCollision = true; } } + if (hasCollision) { + Debug.assert(isNamedDeclaration(node) && isIdentifier(node.name), "The target of a Reflect collision check should be an identifier"); + errorSkippedOn("noEmit", node, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_when_emitting_super_references_in_static_initializers, + declarationNameToString(node.name), + "Reflect"); + } } - 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 checkCollisionsForDeclarationName(node: Node, name: Identifier | undefined) { + if (!name) return; + checkCollisionWithRequireExportsInGeneratedCode(node, name); + checkCollisionWithGlobalPromiseInGeneratedCode(node, name); + recordPotentialCollisionWithWeakMapSetInGeneratedCode(node, name); + recordPotentialCollisionWithReflectInGeneratedCode(node, name); + if (isClassLike(node)) { + checkTypeNameIsReserved(name, Diagnostics.Class_name_cannot_be_0); + if (!(node.flags & NodeFlags.Ambient)) { + checkClassNameCollisionWithObject(name); + } + } + else if (isEnumDeclaration(node)) { + checkTypeNameIsReserved(name, Diagnostics.Enum_name_cannot_be_0); } } - 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)); + 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; } - 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); + // 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 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)); + const symbol = getSymbolOfDeclaration(node); + if (symbol.flags & SymbolFlags.FunctionScopedVariable) { + if (!isIdentifier(node.name)) return Debug.fail(); + const localDeclarationSymbol = resolveName(node, node.name.escapedText, SymbolFlags.Variable, /*nameNotFoundMessage*/ 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 checkJSDocAccessibilityModifiers(node: JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag): void { - const host = getJSDocHost(node); - if (host && isPrivateIdentifierClassElementDeclaration(host)) { - error(node, Diagnostics.An_accessibility_modifier_cannot_be_used_with_a_private_identifier); - } + function convertAutoToAny(type: Type) { + return type === autoType ? anyType : type === autoArrayType ? anyArrayType : type; } - 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; + // Check variable, parameter, or property declaration + function checkVariableLikeDeclaration(node: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement) { + checkDecorators(node); + if (!isBindingElement(node)) { + checkSourceElement(node.type); } - } - function checkFunctionOrMethodDeclaration(node: FunctionDeclaration | MethodDeclaration | MethodSignature): void { - checkDecorators(node); - checkSignatureDeclaration(node); - const functionFlags = getFunctionFlags(node); + // 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 && node.name.kind === SyntaxKind.ComputedPropertyName) { - // This check will account for methods in class/interface declarations, - // as well as accessors in classes/object literals + if (node.name.kind === SyntaxKind.ComputedPropertyName) { checkComputedPropertyName(node.name); + if (hasOnlyExpressionInitializer(node) && node.initializer) { + checkExpressionCached(node.initializer); + } } - if (hasBindableName(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 = getSymbolOfDeclaration(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 = localSymbol.declarations?.find( - // Get first non javascript function declaration - declaration => declaration.kind === node.kind && !(declaration.flags & NodeFlags.JavaScriptFile)); + if (isBindingElement(node)) { + if ( + node.propertyName && + isIdentifier(node.name) && + isParameterDeclaration(node) && + nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) { + // type F = ({a: string}) => void; + // ^^^^^^ + // variable renaming in function type notation is confusing, + // so we forbid it even if noUnusedLocals is not enabled + potentialUnusedRenamedBindingElementsInTypes.push(node); + return; + } - // Only type check the symbol once - if (node === firstDeclaration) { - checkFunctionOrConstructorSymbol(localSymbol); + if (isObjectBindingPattern(node.parent) && node.dotDotDotToken && languageVersion < ScriptTarget.ES2018) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.Rest); + } + // check computed properties inside property names of binding elements + if (node.propertyName && node.propertyName.kind === SyntaxKind.ComputedPropertyName) { + checkComputedPropertyName(node.propertyName); } - if (symbol.parent) { - // run check on export symbol to check that modifiers agree across all exported declarations - checkFunctionOrConstructorSymbol(symbol); + // check private/protected variable access + const parent = node.parent.parent; + const parentCheckMode = node.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal; + const parentType = getTypeForBindingElementParent(parent, parentCheckMode); + 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, /*isSelfTypeAccess*/ false); // A destructuring is never a write-only reference. + checkPropertyAccessibility(node, !!parent.initializer && parent.initializer.kind === SyntaxKind.SuperKeyword, /*writing*/ false, parentType, property); + } + } } } - const body = node.kind === SyntaxKind.MethodSignature ? undefined : node.body; - checkSourceElement(body); - checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, getReturnTypeFromAnnotation(node)); - - addLazyDiagnostic(checkFunctionOrMethodDeclarationDiagnostics); + // 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); + } - // 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.typeExpression.type, Diagnostics.The_type_of_a_function_declaration_must_match_the_function_s_signature); + 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 (isParameter(node) && node.initializer && 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)) { + if (isInAmbientOrTypeNode(node)) { + return; + } + const needCheckInitializer = hasOnlyExpressionInitializer(node) && node.initializer && node.parent.parent.kind !== SyntaxKind.ForInStatement; + const needCheckWidenedType = !some(node.name.elements, not(isOmittedExpression)); + 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 = getSymbolOfDeclaration(node); + if (symbol.flags & SymbolFlags.Alias && (isVariableDeclarationInitializedToBareOrAccessedRequire(node) || isBindingElementOfBareOrAccessedRequire(node))) { + checkAliasSymbol(node); + return; } - function checkFunctionOrMethodDeclarationDiagnostics() { - if (!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); + 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 = hasOnlyExpressionInitializer(node) && 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 (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)); + } + if (symbol.declarations && 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)); } } } - } - - function registerForUnusedIdentifiersCheck(node: PotentiallyUnusedIdentifier): void { - addLazyDiagnostic(registerForUnusedIdentifiersCheckDiagnostics); + 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 (!isErrorType(type) && !isErrorType(declarationType) && + !isTypeIdenticalTo(type, declarationType) && + !(symbol.flags & SymbolFlags.Assignment)) { + errorNextVariableOrPropertyDeclarationMustHaveSameType(symbol.valueDeclaration, type, node, declarationType); + } + if (hasOnlyExpressionInitializer(node) && node.initializer) { + checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(node.initializer), declarationType, node, node.initializer, /*headMessage*/ undefined); + } + if (symbol.valueDeclaration && !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); + } + checkCollisionsForDeclarationName(node, node.name); + } + } + + 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; + } - function registerForUnusedIdentifiersCheckDiagnostics() { - // May be in a call such as getTypeOfNode that happened to call this. But potentiallyUnusedIdentifiers is only defined in the scope of `checkSourceFile`. - 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); + if (hasQuestionToken(left) !== hasQuestionToken(right)) { + return false; } - } - type PotentiallyUnusedIdentifier = - | SourceFile | ModuleDeclaration | ClassLikeDeclaration | InterfaceDeclaration - | Block | CaseBlock | ForStatement | ForInStatement | ForOfStatement - | Exclude | TypeAliasDeclaration - | InferTypeNode; + const interestingFlags = ModifierFlags.Private | + ModifierFlags.Protected | + ModifierFlags.Async | + ModifierFlags.Abstract | + ModifierFlags.Readonly | + ModifierFlags.Static; - 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"); + 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, path: (node as TracingNode).tracingPath }); + if (isTailRec(node)) { + checkTailRecVariableDeclaration(node); + } + checkGrammarVariableDeclaration(node); + checkVariableLikeDeclaration(node); + tracing?.pop(); + addLazyDiagnostic(checkDeferred) + function checkDeferred() { + const links = getNodeLinks(node); + if (links.tsPlusPipeableExtension) { + checkFluentPipeableAgreement(links.tsPlusPipeableExtension); } } } - 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 checkBindingElement(node: BindingElement) { + checkGrammarBindingElement(node); + return checkVariableLikeDeclaration(node); } - function isIdentifierThatStartsWithUnderscore(node: Node) { - return isIdentifier(node) && idText(node).charCodeAt(0) === CharacterCodes._; + function checkVariableStatement(node: VariableStatement) { + // Grammar checking + if (!checkGrammarModifiers(node) && !checkGrammarVariableDeclarationList(node.declarationList)) checkGrammarForDisallowedLetOrConstStatement(node); + forEach(node.declarationList.declarations, checkSourceElement); } - 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 = getSymbolOfDeclaration(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 as ConstructorDeclaration).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: - case SyntaxKind.ClassStaticBlockDeclaration: - // Can't be private - break; - default: - Debug.fail("Unexpected class member"); - } - } + function checkExpressionStatement(node: ExpressionStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + + checkExpression(node.expression); } - 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 checkIfStatement(node: IfStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); + const type = checkTruthinessExpression(node.expression); + checkTestingKnownTruthyCallableOrAwaitableType(node.expression, type, node.thenStatement); + 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 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. - const declarations = getSymbolOfDeclaration(node).declarations; - if (!declarations || last(declarations) !== node) return; + function checkTestingKnownTruthyCallableOrAwaitableType(condExpr: Expression, condType: Type, body?: Statement | Expression) { + if (!strictNullChecks) return; + bothHelper(condExpr, body); - const typeParameters = getEffectiveTypeParameterDeclarations(node); - const seenParentsWithEveryUnused = new Set(); + function bothHelper(condExpr: Expression, body: Expression | Statement | undefined) { + condExpr = skipParentheses(condExpr); - for (const typeParameter of typeParameters) { - if (!isTypeParameterUnused(typeParameter)) continue; + helper(condExpr, body); - const name = idText(typeParameter.name); - const { parent } = typeParameter; - if (parent.kind !== SyntaxKind.InferType && parent.typeParameters!.every(isTypeParameterUnused)) { - if (tryAddToSet(seenParentsWithEveryUnused, parent)) { - const sourceFile = getSourceFileOfNode(parent); - const range = isJSDocTemplateTag(parent) - // Whole @template tag - ? rangeOfNode(parent) - // Include the `<>` in the error message - : rangeOfTypeParameters(sourceFile, parent.typeParameters!); - const only = parent.typeParameters!.length === 1; - //TODO: following line is possible reason for bug #41974, unusedTypeParameters_TemplateTag - const messageAndArg: DiagnosticAndArguments = only - ? [Diagnostics._0_is_declared_but_its_value_is_never_read, name] - : [Diagnostics.All_type_parameters_are_unused]; - addDiagnostic(typeParameter, UnusedKind.Parameter, createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, ...messageAndArg)); - } - } - else { - //TODO: following line is possible reason for bug #41974, unusedTypeParameters_TemplateTag - addDiagnostic(typeParameter, UnusedKind.Parameter, createDiagnosticForNode(typeParameter, Diagnostics._0_is_declared_but_its_value_is_never_read, name)); + while (isBinaryExpression(condExpr) && (condExpr.operatorToken.kind === SyntaxKind.BarBarToken || condExpr.operatorToken.kind === SyntaxKind.QuestionQuestionToken)) { + condExpr = skipParentheses(condExpr.left); + helper(condExpr, body); } } - } - function isTypeParameterUnused(typeParameter: TypeParameterDeclaration): boolean { - return !(getMergedSymbol(typeParameter.symbol).isReferenced! & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderscore(typeParameter.name); - } - - function addToGroup(map: Map, 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 helper(condExpr: Expression, body: Expression | Statement | undefined) { + const location = isLogicalOrCoalescingBinaryExpression(condExpr) ? skipParentheses(condExpr.right) : condExpr; + if (isModuleExportsAccessExpression(location)) { + return; + } + if (isLogicalOrCoalescingBinaryExpression(location)) { + bothHelper(location, body); + return; + } + const type = location === condExpr ? condType : checkTruthinessExpression(location); + const isPropertyExpressionCast = isPropertyAccessExpression(location) && isTypeAssertion(location.expression); + if (!(getTypeFacts(type) & TypeFacts.Truthy) || isPropertyExpressionCast) return; - function isValidUnusedLocalDeclaration(declaration: Declaration): boolean { - if (isBindingElement(declaration)) { - if (isObjectBindingPattern(declaration.parent)) { - /** - * ignore starts with underscore names _ - * const { a: _a } = { a: 1 } - */ - return !!(declaration.propertyName && isIdentifierThatStartsWithUnderscore(declaration.name)); + // While it technically should be invalid for any known-truthy value + // to be tested, we de-scope to functions and Promises unreferenced 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); + const isPromise = !!getAwaitedTypeOfPromise(type); + if (callSignatures.length === 0 && !isPromise) { + return; } - return isIdentifierThatStartsWithUnderscore(declaration.name); - } - return isAmbientModule(declaration) || - (isVariableDeclaration(declaration) && isForInOrOfStatement(declaration.parent.parent) || isImportedDeclaration(declaration)) && isIdentifierThatStartsWithUnderscore(declaration.name!); - } - function checkUnusedLocalsAndParameters(nodeWithLocals: HasLocals, 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) { + const testedNode = isIdentifier(location) ? location + : isPropertyAccessExpression(location) ? location.name + : undefined; + const testedSymbol = testedNode && getSymbolAtLocation(testedNode); + if (!testedSymbol && !isPromise) { return; } - if (local.declarations) { - for (const declaration of local.declarations) { - if (isValidUnusedLocalDeclaration(declaration)) { - continue; - } + const isUsed = testedSymbol && isBinaryExpression(condExpr.parent) && isSymbolUsedInBinaryExpressionChain(condExpr.parent, testedSymbol) + || testedSymbol && body && isSymbolUsedInConditionBody(condExpr, body, testedNode, testedSymbol); + if (!isUsed) { + if (isPromise) { + errorAndMaybeSuggestAwait( + location, + /*maybeMissingAwait*/ true, + Diagnostics.This_condition_will_always_return_true_since_this_0_is_always_defined, + getTypeNameForErrorDisplay(type)); + } + else { + error(location, Diagnostics.This_condition_will_always_return_true_since_this_function_is_always_defined_Did_you_mean_to_call_it_instead); + } + } + } + } - if (isImportedDeclaration(declaration)) { - const importClause = importClauseFromImported(declaration); - if (!importClause.parent.isTsPlusGlobal) { - addToGroup(unusedImports, importClause, declaration, getNodeId); - } + function isSymbolUsedInConditionBody(expr: Expression, body: Statement | Expression, testedNode: Node, testedSymbol: Symbol): boolean { + return !!forEachChild(body, function check(childNode): boolean | undefined { + if (isIdentifier(childNode)) { + const childSymbol = getSymbolAtLocation(childNode); + if (childSymbol && childSymbol === testedSymbol) { + // If the test was a simple identifier, the above check is sufficient + if (isIdentifier(expr) || isIdentifier(testedNode) && isBinaryExpression(testedNode.parent)) { + return true; } - 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); + // 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); } - } - 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)) { - if (isBindingElement(declaration) && isArrayBindingPattern(declaration.parent)) { - addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId); - } - else { - addDiagnostic(parameter, UnusedKind.Parameter, createDiagnosticForNode(name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(local))); - } + else if (isPropertyAccessExpression(testedExpression) && isPropertyAccessExpression(childExpression)) { + if (getSymbolAtLocation(testedExpression.name) !== getSymbolAtLocation(childExpression.name)) { + return false; } + childExpression = childExpression.expression; + testedExpression = testedExpression.expression; + } + else if (isCallExpression(testedExpression) && isCallExpression(childExpression)) { + childExpression = childExpression.expression; + testedExpression = testedExpression.expression; } else { - errorUnusedLocal(declaration, symbolName(local), addDiagnostic); + return false; } } } } - }); - 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))); - } - } + return forEachChild(childNode, check); }); } - function checkPotentialUncheckedRenamedBindingElementsInTypes() { - for (const node of potentialUnusedRenamedBindingElementsInTypes) { - if (!getSymbolOfDeclaration(node)?.isReferenced) { - const wrappingDeclaration = walkUpBindingElementsAndPatterns(node); - Debug.assert(isParameterDeclaration(wrappingDeclaration), "Only parameter declaration should be checked here"); - const diagnostic = createDiagnosticForNode(node.name, Diagnostics._0_is_an_unused_renaming_of_1_Did_you_intend_to_use_it_as_a_type_annotation, declarationNameToString(node.name), declarationNameToString(node.propertyName)); - if (!wrappingDeclaration.type) { - // entire parameter does not have type annotation, suggest adding an annotation - addRelatedInfo( - diagnostic, - createFileDiagnostic(getSourceFileOfNode(wrappingDeclaration), wrappingDeclaration.end, 1, Diagnostics.We_can_only_write_a_type_for_0_by_adding_a_type_for_the_entire_parameter_here, declarationNameToString(node.propertyName)) - ); + function isSymbolUsedInBinaryExpressionChain(node: Node, testedSymbol: Symbol): boolean { + while (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { + const isUsed = forEachChild(node.right, function visit(child): boolean | undefined { + if (isIdentifier(child)) { + const symbol = getSymbolAtLocation(child); + if (symbol && symbol === testedSymbol) { + return true; + } } - diagnostics.add(diagnostic); + return forEachChild(child, visit); + }); + if (isUsed) { + return true; } + node = node.parent; } + return false; } - 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 checkDoStatement(node: DoStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); - 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); - } + checkSourceElement(node.statement); + checkTruthinessExpression(node.expression); } - 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 as FunctionLikeDeclaration).body)) { - return; - } + function checkWhileStatement(node: WhileStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); - 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); - } - }); + checkTruthinessExpression(node.expression); + checkSourceElement(node.statement); } - /** - * Checks whether an {@link Identifier}, in the context of another {@link Node}, would collide with a runtime value - * of {@link name} in an outer scope. This is used to check for collisions for downlevel transformations that - * require names like `Object`, `Promise`, `Reflect`, `require`, `exports`, etc. - */ - function needCollisionCheckForIdentifier(node: Node, identifier: Identifier | undefined, name: string): boolean { - if (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 || - node.kind === SyntaxKind.PropertyAssignment) { - // it is ok to have member named '_super', '_this', `Promise`, etc. - member access is always qualified - return false; + 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; + } - if (node.flags & NodeFlags.Ambient) { - // ambient context - no codegen impact - return false; - } + function checkTruthinessExpression(node: Expression, checkMode?: CheckMode) { + return checkTruthinessOfType(checkExpression(node, checkMode), node); + } - if (isImportClause(node) || isImportEqualsDeclaration(node) || isImportSpecifier(node)) { - // type-only imports do not require collision checks against runtime values. - if (isTypeOnlyImportOrExportDeclaration(node)) { - return false; + function checkForStatement(node: ForStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) { + if (node.initializer && node.initializer.kind === SyntaxKind.VariableDeclarationList) { + checkGrammarVariableDeclarationList(node.initializer as VariableDeclarationList); } } - const root = getRootDeclaration(node); - if (isParameter(root) && nodeIsMissing((root.parent as FunctionLikeDeclaration).body)) { - // just an overload - no codegen impact - return false; + if (node.initializer) { + if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { + forEach((node.initializer as VariableDeclarationList).declarations, checkVariableDeclaration); + } + else { + checkExpression(node.initializer); + } } - return true; + if (node.condition) checkTruthinessExpression(node.condition); + if (node.incrementor) checkExpression(node.incrementor); + checkSourceElement(node.statement); + if (node.locals) { + registerForUnusedIdentifiersCheck(node); + } } - // 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 as Declaration), 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 checkForOfStatement(node: ForOfStatement): void { + checkGrammarForInOrForOfStatement(node); - function checkIfNewTargetIsCapturedInEnclosingScope(node: Node): void { - findAncestor(node, current => { - if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureNewTarget) { - const isDeclaration = node.kind !== SyntaxKind.Identifier; - if (isDeclaration) { - error(getNameOfDeclaration(node as Declaration), 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); + const container = getContainingFunctionOrClassStaticBlock(node); + if (node.awaitModifier) { + if (container && isClassStaticBlockDeclaration(container)) { + grammarErrorOnNode(node.awaitModifier, Diagnostics.For_await_loops_cannot_be_used_inside_a_class_static_block); + } + else { + const functionFlags = getFunctionFlags(container); + 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); } - return true; } - return false; - }); - } - - function checkCollisionWithRequireExportsInGeneratedCode(node: Node, name: Identifier | undefined) { - // No need to check for require or exports for ES6 modules and later - if (moduleKind >= ModuleKind.ES2015 && !(moduleKind >= ModuleKind.Node16 && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { - return; + } + else if (compilerOptions.downlevelIteration && languageVersion < ScriptTarget.ES2015) { + // for..of prior to ES2015 requires the __values helper when downlevelIteration is enabled + checkExternalEmitHelpers(node, ExternalEmitHelpers.ForOfIncludes); } - if (!name || !needCollisionCheckForIdentifier(node, name, "require") && !needCollisionCheckForIdentifier(node, name, "exports")) { - return; + // 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); - // Uninstantiated modules shouldnt do this check - if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { - return; + // 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); + } + } } - // 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 as SourceFile)) { - // 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)); + checkSourceElement(node.statement); + if (node.locals) { + registerForUnusedIdentifiersCheck(node); } } - function checkCollisionWithGlobalPromiseInGeneratedCode(node: Node, name: Identifier | undefined): void { - if (!name || languageVersion >= ScriptTarget.ES2017 || !needCollisionCheckForIdentifier(node, name, "Promise")) { - return; + 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 as VariableDeclarationList).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); + } } - // Uninstantiated modules shouldnt do this check - if (isModuleDeclaration(node) && getModuleInstanceState(node) !== ModuleInstanceState.Instantiated) { - return; + // 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)); } - // 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 as SourceFile) && 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)); + checkSourceElement(node.statement); + if (node.locals) { + registerForUnusedIdentifiersCheck(node); } } - function recordPotentialCollisionWithWeakMapSetInGeneratedCode(node: Node, name: Identifier): void { - if (languageVersion <= ScriptTarget.ES2021 - && (needCollisionCheckForIdentifier(node, name, "WeakMap") || needCollisionCheckForIdentifier(node, name, "WeakSet"))) { - potentialWeakMapSetCollisions.push(node); + function checkForInOrForOfVariableDeclaration(iterationStatement: ForInOrOfStatement): void { + const variableDeclarationList = iterationStatement.initializer as VariableDeclarationList; + // checkGrammarForInOrForOfStatement will check that there is exactly one declaration. + if (variableDeclarationList.declarations.length >= 1) { + const decl = variableDeclarationList.declarations[0]; + checkVariableDeclaration(decl); } } - function checkWeakMapSetCollision(node: Node) { - const enclosingBlockScope = getEnclosingBlockScopeContainer(node); - if (getNodeCheckFlags(enclosingBlockScope) & NodeCheckFlags.ContainsClassWithPrivateIdentifiers) { - Debug.assert(isNamedDeclaration(node) && isIdentifier(node.name) && typeof node.name.escapedText === "string", "The target of a WeakMap/WeakSet collision check should be an identifier"); - errorSkippedOn("noEmit", node, Diagnostics.Compiler_reserves_name_0_when_emitting_private_identifier_downlevel, node.name.escapedText); - } + function checkRightHandSideOfForOf(statement: ForOfStatement): Type { + const use = statement.awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf; + return checkIteratedTypeOrElementType(use, checkNonNullExpression(statement.expression), undefinedType, statement.expression); } - function recordPotentialCollisionWithReflectInGeneratedCode(node: Node, name: Identifier | undefined): void { - if (name && languageVersion >= ScriptTarget.ES2015 && languageVersion <= ScriptTarget.ES2021 - && needCollisionCheckForIdentifier(node, name, "Reflect")) { - potentialReflectCollisions.push(node); + 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; } - function checkReflectCollision(node: Node) { - let hasCollision = false; - if (isClassExpression(node)) { - // ClassExpression names don't contribute to their containers, but do matter for any of their block-scoped members. - for (const member of node.members) { - if (getNodeCheckFlags(member) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { - hasCollision = true; - break; + /** + * 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); + } } } - } - else if (isFunctionExpression(node)) { - // FunctionExpression names don't contribute to their containers, but do matter for their contents - if (getNodeCheckFlags(node) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { - hasCollision = true; + if (iterationTypes || uplevelIteration) { + return possibleOutOfBounds ? includeUndefinedInIndexSignature(iterationTypes && iterationTypes.yieldType) : (iterationTypes && iterationTypes.yieldType); } } - else { - const container = getEnclosingBlockScopeContainer(node); - if (container && getNodeCheckFlags(container) & NodeCheckFlags.ContainsSuperPropertyInStaticInitializer) { - hasCollision = true; + + 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 as UnionType).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; } - } - if (hasCollision) { - Debug.assert(isNamedDeclaration(node) && isIdentifier(node.name), "The target of a Reflect collision check should be an identifier"); - errorSkippedOn("noEmit", node, Diagnostics.Duplicate_identifier_0_Compiler_reserves_name_1_when_emitting_super_references_in_static_initializers, - declarationNameToString(node.name), - "Reflect"); - } - } - function checkCollisionsForDeclarationName(node: Node, name: Identifier | undefined) { - if (!name) return; - checkCollisionWithRequireExportsInGeneratedCode(node, name); - checkCollisionWithGlobalPromiseInGeneratedCode(node, name); - recordPotentialCollisionWithWeakMapSetInGeneratedCode(node, name); - recordPotentialCollisionWithReflectInGeneratedCode(node, name); - if (isClassLike(node)) { - checkTypeNameIsReserved(name, Diagnostics.Class_name_cannot_be_0); - if (!(node.flags & NodeFlags.Ambient)) { - checkClassNameCollisionWithObject(name); + 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; + } } } - else if (isEnumDeclaration(node)) { - checkTypeNameIsReserved(name, Diagnostics.Enum_name_cannot_be_0); - } - } - - 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. + 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 allowsStrings = !!(use & IterationUse.AllowsStringInputFlag) && !hasStringConstituent; + const [defaultDiagnostic, maybeMissingAwait] = getIterationDiagnosticDetails(allowsStrings, downlevelIteration); + errorAndMaybeSuggestAwait( + errorNode, + maybeMissingAwait && !!getAwaitedTypeOfPromise(arrayType), + defaultDiagnostic, + typeToString(arrayType)); + } + return hasStringConstituent ? possibleOutOfBounds ? includeUndefinedInIndexSignature(stringType) : stringType : undefined; + } - // 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' - // } + const arrayElementType = getIndexTypeOfType(arrayType, numberType); + 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; + } - // skip block-scoped variables and parameters - if ((getCombinedNodeFlags(node) & NodeFlags.BlockScoped) !== 0 || isParameterDeclaration(node)) { - return; + return getUnionType(possibleOutOfBounds ? [arrayElementType, stringType, undefinedType] : [arrayElementType, stringType], UnionReduction.Subtype); } - // 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; - } + return (use & IterationUse.PossiblyOutOfBounds) ? includeUndefinedInIndexSignature(arrayElementType) : arrayElementType; - const symbol = getSymbolOfDeclaration(node); - if (symbol.flags & SymbolFlags.FunctionScopedVariable) { - if (!isIdentifier(node.name)) return Debug.fail(); - const localDeclarationSymbol = resolveName(node, node.name.escapedText, SymbolFlags.Variable, /*nameNotFoundMessage*/ 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; + function getIterationDiagnosticDetails(allowsStrings: boolean, downlevelIteration: boolean | undefined): [error: DiagnosticMessage, maybeMissingAwait: boolean] { + if (downlevelIteration) { + return allowsStrings + ? [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] + : [Diagnostics.Type_0_is_not_an_array_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true]; + } - // 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); + const yieldType = getIterationTypeOfIterable(use, IterationTypeKind.Yield, inputType, /*errorNode*/ undefined); - // 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); - } - } + if (yieldType) { + return [Diagnostics.Type_0_can_only_be_iterated_through_when_using_the_downlevelIteration_flag_or_with_a_target_of_es2015_or_higher, false]; } - } - } - function convertAutoToAny(type: Type) { - return type === autoType ? anyType : type === autoArrayType ? anyArrayType : type; - } + if (isES2015OrLaterIterable(inputType.symbol?.escapedName)) { + return [Diagnostics.Type_0_can_only_be_iterated_through_when_using_the_downlevelIteration_flag_or_with_a_target_of_es2015_or_higher, true]; + } - // Check variable, parameter, or property declaration - function checkVariableLikeDeclaration(node: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement) { - checkDecorators(node); - if (!isBindingElement(node)) { - checkSourceElement(node.type); + return allowsStrings + ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type, true] + : [Diagnostics.Type_0_is_not_an_array_type, true]; } + } - // JSDoc `function(string, string): string` syntax results in parameters with no name - if (!node.name) { - return; + function isES2015OrLaterIterable(n: __String) { + switch (n) { + case "Float32Array": + case "Float64Array": + case "Int16Array": + case "Int32Array": + case "Int8Array": + case "NodeList": + case "Uint16Array": + case "Uint32Array": + case "Uint8Array": + case "Uint8ClampedArray": + return true; } + return false; + } - // 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 (hasOnlyExpressionInitializer(node) && node.initializer) { - checkExpressionCached(node.initializer); - } + /** + * 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; } - if (isBindingElement(node)) { - if ( - node.propertyName && - isIdentifier(node.name) && - isParameterDeclaration(node) && - nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) { - // type F = ({a: string}) => void; - // ^^^^^^ - // variable renaming in function type notation is confusing, - // so we forbid it even if noUnusedLocals is not enabled - potentialUnusedRenamedBindingElementsInTypes.push(node); - return; - } + const iterationTypes = getIterationTypesOfIterable(inputType, use, errorNode); + return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(typeKind)]; + } - if (isObjectBindingPattern(node.parent) && node.dotDotDotToken && languageVersion < ScriptTarget.ES2018) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.Rest); - } - // check computed properties inside property names of binding elements - if (node.propertyName && node.propertyName.kind === SyntaxKind.ComputedPropertyName) { - checkComputedPropertyName(node.propertyName); + 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 }; + } - // check private/protected variable access - const parent = node.parent.parent; - const parentCheckMode = node.dotDotDotToken ? CheckMode.RestBindingElement : CheckMode.Normal; - const parentType = getTypeForBindingElementParent(parent, parentCheckMode); - 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, /*isSelfTypeAccess*/ false); // A destructuring is never a write-only reference. - checkPropertyAccessibility(node, !!parent.initializer && parent.initializer.kind === SyntaxKind.SuperKeyword, /*writing*/ false, parentType, property); - } - } + /** + * 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; + } - // 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); - } + function getCachedIterationTypes(type: Type, cacheKey: MatchingKeys) { + return (type as IterableOrIteratorType)[cacheKey]; + } - 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 (isParameter(node) && node.initializer && nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) { - error(node, Diagnostics.A_parameter_initializer_is_only_allowed_in_a_function_or_constructor_implementation); - return; + 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; } - // For a binding pattern, validate the initializer and exit - if (isBindingPattern(node.name)) { - if (isInAmbientOrTypeNode(node)) { - return; - } - const needCheckInitializer = hasOnlyExpressionInitializer(node) && node.initializer && node.parent.parent.kind !== SyntaxKind.ForInStatement; - const needCheckWidenedType = !some(node.name.elements, not(isOmittedExpression)); - 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); + + if (!(type.flags & TypeFlags.Union)) { + const errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined = errorNode ? { errors: undefined } : undefined; + const iterationTypes = getIterationTypesOfIterableWorker(type, use, errorNode, errorOutputContainer); + if (iterationTypes === noIterationTypes) { + if (errorNode) { + const rootDiag = reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); + if (errorOutputContainer?.errors) { + addRelatedInfo(rootDiag, ...errorOutputContainer.errors); } } - // 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 undefined; + } + else if (errorOutputContainer?.errors?.length) { + for (const diag of errorOutputContainer.errors) { + diagnostics.add(diag); } } - return; - } - // For a commonjs `const x = require`, validate the alias and exit - const symbol = getSymbolOfDeclaration(node); - if (symbol.flags & SymbolFlags.Alias && (isVariableDeclarationInitializedToBareOrAccessedRequire(node) || isBindingElementOfBareOrAccessedRequire(node))) { - checkAliasSymbol(node); - return; + return iterationTypes; } - 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 = hasOnlyExpressionInitializer(node) && 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); + 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 errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined = errorNode ? { errors: undefined } : undefined; + const iterationTypes = getIterationTypesOfIterableWorker(constituent, use, errorNode, errorOutputContainer); + if (iterationTypes === noIterationTypes) { + if (errorNode) { + const rootDiag = reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); + if (errorOutputContainer?.errors) { + addRelatedInfo(rootDiag, ...errorOutputContainer.errors); + } } + setCachedIterationTypes(type, cacheKey, noIterationTypes); + return undefined; } - if (symbol.declarations && 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 if (errorOutputContainer?.errors?.length) { + for (const diag of errorOutputContainer.errors) { + diagnostics.add(diag); } } - } - 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 (!isErrorType(type) && !isErrorType(declarationType) && - !isTypeIdenticalTo(type, declarationType) && - !(symbol.flags & SymbolFlags.Assignment)) { - errorNextVariableOrPropertyDeclarationMustHaveSameType(symbol.valueDeclaration, type, node, declarationType); - } - if (hasOnlyExpressionInitializer(node) && node.initializer) { - checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(node.initializer), declarationType, node, node.initializer, /*headMessage*/ undefined); - } - if (symbol.valueDeclaration && !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); - } - checkCollisionsForDeclarationName(node, node.name); + allIterationTypes = append(allIterationTypes, iterationTypes); } + + const iterationTypes = allIterationTypes ? combineIterationTypes(allIterationTypes) : noIterationTypes; + setCachedIterationTypes(type, cacheKey, iterationTypes); + return iterationTypes === noIterationTypes ? undefined : iterationTypes; } - 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 getAsyncFromSyncIterationTypes(iterationTypes: IterationTypes, errorNode: Node | undefined) { + if (iterationTypes === noIterationTypes) return noIterationTypes; + if (iterationTypes === anyIterationTypes) return anyIterationTypes; + const { yieldType, returnType, nextType } = iterationTypes; + // if we're requesting diagnostics, report errors for a missing `Awaited`. + if (errorNode) { + getGlobalAwaitedSymbol(/*reportErrors*/ true); } + return createIterationTypes( + getAwaitedType(yieldType, errorNode) || anyType, + getAwaitedType(returnType, errorNode) || anyType, + nextType); } - 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; + /** + * 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, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined) { + if (isTypeAny(type)) { + return anyIterationTypes; } - if (hasQuestionToken(left) !== hasQuestionToken(right)) { - return false; - } + // If we are reporting errors and encounter a cached `noIterationTypes`, we should ignore the cached value and continue as if nothing was cached. + // In addition, we should not cache any new results for this call. + let noCache = false; - const interestingFlags = ModifierFlags.Private | - ModifierFlags.Protected | - ModifierFlags.Async | - ModifierFlags.Abstract | - ModifierFlags.Readonly | - ModifierFlags.Static; + if (use & IterationUse.AllowsAsyncIterablesFlag) { + const iterationTypes = + getIterationTypesOfIterableCached(type, asyncIterationTypesResolver) || + getIterationTypesOfIterableFast(type, asyncIterationTypesResolver); + if (iterationTypes) { + if (iterationTypes === noIterationTypes && errorNode) { + // ignore the cached value + noCache = true; + } + else { + return use & IterationUse.ForOfFlag ? + getAsyncFromSyncIterationTypes(iterationTypes, errorNode) : + iterationTypes; + } + } + } - return getSelectedEffectiveModifierFlags(left, interestingFlags) === getSelectedEffectiveModifierFlags(right, interestingFlags); - } + if (use & IterationUse.AllowsSyncIterablesFlag) { + let iterationTypes = + getIterationTypesOfIterableCached(type, syncIterationTypesResolver) || + getIterationTypesOfIterableFast(type, syncIterationTypesResolver); + if (iterationTypes) { + if (iterationTypes === noIterationTypes && errorNode) { + // ignore the cached value + noCache = true; + } + else { + if (use & IterationUse.AllowsAsyncIterablesFlag) { + // for a sync iterable in an async context, only use the cached types if they are valid. + if (iterationTypes !== noIterationTypes) { + iterationTypes = getAsyncFromSyncIterationTypes(iterationTypes, errorNode); + return noCache ? iterationTypes : setCachedIterationTypes(type, "iterationTypesOfAsyncIterable", iterationTypes); + } + } + else { + return iterationTypes; + } + } + } + } - function checkVariableDeclaration(node: VariableDeclaration) { - tracing?.push(tracing.Phase.Check, "checkVariableDeclaration", { kind: node.kind, pos: node.pos, end: node.end, path: (node as TracingNode).tracingPath }); - if (isTailRec(node)) { - checkTailRecVariableDeclaration(node); + if (use & IterationUse.AllowsAsyncIterablesFlag) { + const iterationTypes = getIterationTypesOfIterableSlow(type, asyncIterationTypesResolver, errorNode, errorOutputContainer, noCache); + if (iterationTypes !== noIterationTypes) { + return iterationTypes; + } } - checkGrammarVariableDeclaration(node); - checkVariableLikeDeclaration(node); - tracing?.pop(); - addLazyDiagnostic(checkDeferred) - function checkDeferred() { - const links = getNodeLinks(node); - if (links.tsPlusPipeableExtension) { - checkFluentPipeableAgreement(links.tsPlusPipeableExtension); + + if (use & IterationUse.AllowsSyncIterablesFlag) { + let iterationTypes = getIterationTypesOfIterableSlow(type, syncIterationTypesResolver, errorNode, errorOutputContainer, noCache); + if (iterationTypes !== noIterationTypes) { + if (use & IterationUse.AllowsAsyncIterablesFlag) { + iterationTypes = getAsyncFromSyncIterationTypes(iterationTypes, errorNode); + return noCache ? iterationTypes : setCachedIterationTypes(type, "iterationTypesOfAsyncIterable", iterationTypes); + } + else { + return iterationTypes; + } } } - } - function checkBindingElement(node: BindingElement) { - checkGrammarBindingElement(node); - return checkVariableLikeDeclaration(node); + return noIterationTypes; } - function checkVariableStatement(node: VariableStatement) { - // Grammar checking - if (!checkGrammarModifiers(node) && !checkGrammarVariableDeclarationList(node.declarationList)) checkGrammarForDisallowedLetOrConstStatement(node); - forEach(node.declarationList.declarations, checkSourceElement); + /** + * 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 checkExpressionStatement(node: ExpressionStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); - - checkExpression(node.expression); + function getIterationTypesOfGlobalIterableType(globalType: Type, resolver: IterationTypesResolver) { + const globalIterationTypes = + getIterationTypesOfIterableCached(globalType, resolver) || + getIterationTypesOfIterableSlow(globalType, resolver, /*errorNode*/ undefined, /*errorOutputContainer*/ undefined, /*noCache*/ false); + return globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes; } - function checkIfStatement(node: IfStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); - const type = checkTruthinessExpression(node.expression); - checkTestingKnownTruthyCallableOrAwaitableType(node.expression, type, node.thenStatement); - checkSourceElement(node.thenStatement); - - if (node.thenStatement.kind === SyntaxKind.EmptyStatement) { - error(node.thenStatement, Diagnostics.The_body_of_an_if_statement_cannot_be_the_empty_statement); + /** + * 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(resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || yieldType, resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || returnType, nextType)); } - checkSourceElement(node.elseStatement); + // 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(resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || yieldType, resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || returnType, nextType)); + } } - function checkTestingKnownTruthyCallableOrAwaitableType(condExpr: Expression, condType: Type, body?: Statement | Expression) { - if (!strictNullChecks) return; - bothHelper(condExpr, body); - - function bothHelper(condExpr: Expression, body: Expression | Statement | undefined) { - condExpr = skipParentheses(condExpr); - - helper(condExpr, body); + function getPropertyNameForKnownSymbolName(symbolName: string): __String { + const ctorType = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); + const uniqueType = ctorType && getTypeOfPropertyOfType(getTypeOfSymbol(ctorType), escapeLeadingUnderscores(symbolName)); + return uniqueType && isTypeUsableAsPropertyName(uniqueType) ? getPropertyNameFromType(uniqueType) : `__@${symbolName}` as __String; + } - while (isBinaryExpression(condExpr) && (condExpr.operatorToken.kind === SyntaxKind.BarBarToken || condExpr.operatorToken.kind === SyntaxKind.QuestionQuestionToken)) { - condExpr = skipParentheses(condExpr.left); - helper(condExpr, body); - } + /** + * 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, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined, noCache: boolean) { + const method = getPropertyOfType(type, getPropertyNameForKnownSymbolName(resolver.iteratorSymbolName)); + const methodType = method && !(method.flags & SymbolFlags.Optional) ? getTypeOfSymbol(method) : undefined; + if (isTypeAny(methodType)) { + return noCache ? anyIterationTypes : setCachedIterationTypes(type, resolver.iterableCacheKey, anyIterationTypes); } - function helper(condExpr: Expression, body: Expression | Statement | undefined) { - const location = isLogicalOrCoalescingBinaryExpression(condExpr) ? skipParentheses(condExpr.right) : condExpr; - if (isModuleExportsAccessExpression(location)) { - return; - } - if (isLogicalOrCoalescingBinaryExpression(location)) { - bothHelper(location, body); - return; - } - const type = location === condExpr ? condType : checkTruthinessExpression(location); - const isPropertyExpressionCast = isPropertyAccessExpression(location) && isTypeAssertion(location.expression); - if (!(getTypeFacts(type) & TypeFacts.Truthy) || isPropertyExpressionCast) return; - - // While it technically should be invalid for any known-truthy value - // to be tested, we de-scope to functions and Promises unreferenced 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); - const isPromise = !!getAwaitedTypeOfPromise(type); - if (callSignatures.length === 0 && !isPromise) { - return; - } + const signatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : undefined; + if (!some(signatures)) { + return noCache ? noIterationTypes : setCachedIterationTypes(type, resolver.iterableCacheKey, noIterationTypes); + } - const testedNode = isIdentifier(location) ? location - : isPropertyAccessExpression(location) ? location.name - : undefined; - const testedSymbol = testedNode && getSymbolAtLocation(testedNode); - if (!testedSymbol && !isPromise) { - return; - } + const iteratorType = getIntersectionType(map(signatures, getReturnTypeOfSignature)); + const iterationTypes = getIterationTypesOfIteratorWorker(iteratorType, resolver, errorNode, errorOutputContainer, noCache) ?? noIterationTypes; + return noCache ? iterationTypes : setCachedIterationTypes(type, resolver.iterableCacheKey, iterationTypes); + } - const isUsed = testedSymbol && isBinaryExpression(condExpr.parent) && isSymbolUsedInBinaryExpressionChain(condExpr.parent, testedSymbol) - || testedSymbol && body && isSymbolUsedInConditionBody(condExpr, body, testedNode, testedSymbol); - if (!isUsed) { - if (isPromise) { - errorAndMaybeSuggestAwait( - location, - /*maybeMissingAwait*/ true, - Diagnostics.This_condition_will_always_return_true_since_this_0_is_always_defined, - getTypeNameForErrorDisplay(type)); - } - else { - error(location, Diagnostics.This_condition_will_always_return_true_since_this_function_is_always_defined_Did_you_mean_to_call_it_instead); - } - } - } + function reportTypeNotIterableError(errorNode: Node, type: Type, allowAsyncIterables: boolean): Diagnostic { + 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; + const suggestAwait = + // for (const x of Promise<...>) or [...Promise<...>] + !!getAwaitedTypeOfPromise(type) + // for (const x of AsyncIterable<...>) + || ( + !allowAsyncIterables && + isForOfStatement(errorNode.parent) && + errorNode.parent.expression === errorNode && + getGlobalAsyncIterableType(/*reportErrors*/ false) !== emptyGenericType && + isTypeAssignableTo(type, getGlobalAsyncIterableType(/*reportErrors*/ false) + )); + return errorAndMaybeSuggestAwait(errorNode, suggestAwait, message, typeToString(type)); } - function isSymbolUsedInConditionBody(expr: Expression, body: Statement | Expression, testedNode: Node, testedSymbol: Symbol): boolean { - return !!forEachChild(body, function check(childNode): boolean | undefined { - if (isIdentifier(childNode)) { - const childSymbol = getSymbolAtLocation(childNode); - if (childSymbol && childSymbol === testedSymbol) { - // If the test was a simple identifier, the above check is sufficient - if (isIdentifier(expr) || isIdentifier(testedNode) && isBinaryExpression(testedNode.parent)) { - 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); - } - else if (isPropertyAccessExpression(testedExpression) && isPropertyAccessExpression(childExpression)) { - if (getSymbolAtLocation(testedExpression.name) !== getSymbolAtLocation(childExpression.name)) { - return false; - } - childExpression = childExpression.expression; - testedExpression = testedExpression.expression; - } - else if (isCallExpression(testedExpression) && isCallExpression(childExpression)) { - childExpression = childExpression.expression; - testedExpression = testedExpression.expression; - } - else { - return false; - } - } - } - } - return forEachChild(childNode, check); - }); + /** + * 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, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined) { + return getIterationTypesOfIteratorWorker(type, resolver, errorNode, errorOutputContainer, /*noCache*/ false); } - function isSymbolUsedInBinaryExpressionChain(node: Node, testedSymbol: Symbol): boolean { - while (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { - const isUsed = forEachChild(node.right, function visit(child): boolean | undefined { - if (isIdentifier(child)) { - const symbol = getSymbolAtLocation(child); - if (symbol && symbol === testedSymbol) { - return true; - } - } - return forEachChild(child, visit); - }); - if (isUsed) { - return true; - } - node = node.parent; + /** + * 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. + * + * NOTE: You probably don't want to call this directly and should be calling + * `getIterationTypesOfIterator` instead. + */ + function getIterationTypesOfIteratorWorker(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined, noCache: boolean) { + if (isTypeAny(type)) { + return anyIterationTypes; + } + + let iterationTypes = + getIterationTypesOfIteratorCached(type, resolver) || + getIterationTypesOfIteratorFast(type, resolver); + + if (iterationTypes === noIterationTypes && errorNode) { + iterationTypes = undefined; + noCache = true; } - return false; - } - function checkDoStatement(node: DoStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); + iterationTypes ??= getIterationTypesOfIteratorSlow(type, resolver, errorNode, errorOutputContainer, noCache); + return iterationTypes === noIterationTypes ? undefined : iterationTypes; + } - checkSourceElement(node.statement); - checkTruthinessExpression(node.expression); + /** + * 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); } - function checkWhileStatement(node: WhileStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); + /** + * 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, /*errorOutputContainer*/ undefined, /*noCache*/ false); + 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)); + } + } - checkTruthinessExpression(node.expression); - checkSourceElement(node.statement); + 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 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 isYieldIteratorResult(type: Type) { + return isIteratorResult(type, IterationTypeKind.Yield); } - function checkTruthinessExpression(node: Expression, checkMode?: CheckMode) { - return checkTruthinessOfType(checkExpression(node, checkMode), node); + function isReturnIteratorResult(type: Type) { + return isIteratorResult(type, IterationTypeKind.Return); } - function checkForStatement(node: ForStatement) { - // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) { - if (node.initializer && node.initializer.kind === SyntaxKind.VariableDeclarationList) { - checkGrammarVariableDeclarationList(node.initializer as VariableDeclarationList); - } + /** + * 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; } - if (node.initializer) { - if (node.initializer.kind === SyntaxKind.VariableDeclarationList) { - forEach((node.initializer as VariableDeclarationList).declarations, checkVariableDeclaration); - } - else { - checkExpression(node.initializer); - } + const cachedTypes = getCachedIterationTypes(type, "iterationTypesOfIteratorResult"); + if (cachedTypes) { + return cachedTypes; } - if (node.condition) checkTruthinessExpression(node.condition); - if (node.incrementor) checkExpression(node.incrementor); - checkSourceElement(node.statement); - if (node.locals) { - registerForUnusedIdentifiersCheck(node); + // 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)); } - } - function checkForOfStatement(node: ForOfStatement): void { - checkGrammarForInOrForOfStatement(node); + // 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 container = getContainingFunctionOrClassStaticBlock(node); - if (node.awaitModifier) { - if (container && isClassStaticBlockDeclaration(container)) { - grammarErrorOnNode(node.awaitModifier, Diagnostics.For_await_loops_cannot_be_used_inside_a_class_static_block); - } - else { - const functionFlags = getFunctionFlags(container); - 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); + const returnIteratorResult = filterType(type, isReturnIteratorResult); + const returnType = returnIteratorResult !== neverType ? getTypeOfPropertyOfType(returnIteratorResult, "value" as __String) : undefined; + + if (!yieldType && !returnType) { + return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", noIterationTypes); } - // 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); + // 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, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined): IterationTypes | undefined { + const method = getPropertyOfType(type, methodName as __String); + + // Ignore 'return' or 'throw' if they are missing. + if (!method && methodName !== "next") { + return undefined; } - 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); + const methodType = method && !(methodName === "next" && (method.flags & SymbolFlags.Optional)) + ? methodName === "next" ? getTypeOfSymbol(method) : getTypeWithFacts(getTypeOfSymbol(method), TypeFacts.NEUndefinedOrNull) + : undefined; - // 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); + 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; + if (errorOutputContainer) { + errorOutputContainer.errors ??= []; + errorOutputContainer.errors.push(createDiagnosticForNode(errorNode, diagnostic, methodName)); + } + else { + error(errorNode, diagnostic, methodName); } } + return methodName === "next" ? noIterationTypes : undefined; } - checkSourceElement(node.statement); - if (node.locals) { - registerForUnusedIdentifiersCheck(node); + // 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); + } } - } - - 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 as VariableDeclarationList).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); + // 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)); } - checkForInOrForOfVariableDeclaration(node); + methodReturnTypes = append(methodReturnTypes, getReturnTypeOfSignature(signature)); } - 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); + + // 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 { - // 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); + 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); } } - // 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)); + // 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) { + if (errorOutputContainer) { + errorOutputContainer.errors ??= []; + errorOutputContainer.errors.push(createDiagnosticForNode(errorNode, resolver.mustHaveAValueDiagnostic, methodName)); + } + else { + error(errorNode, resolver.mustHaveAValueDiagnostic, methodName); + } + } + yieldType = anyType; + returnTypes = append(returnTypes, anyType); + } + else { + yieldType = iterationTypes.yieldType; + returnTypes = append(returnTypes, iterationTypes.returnType); } - checkSourceElement(node.statement); - if (node.locals) { - registerForUnusedIdentifiersCheck(node); + 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, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined, noCache: boolean) { + const iterationTypes = combineIterationTypes([ + getIterationTypesOfMethod(type, resolver, "next", errorNode, errorOutputContainer), + getIterationTypesOfMethod(type, resolver, "return", errorNode, errorOutputContainer), + getIterationTypesOfMethod(type, resolver, "throw", errorNode, errorOutputContainer), + ]); + return noCache ? iterationTypes : 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 checkForInOrForOfVariableDeclaration(iterationStatement: ForInOrOfStatement): void { - const variableDeclarationList = iterationStatement.initializer as VariableDeclarationList; - // checkGrammarForInOrForOfStatement will check that there is exactly one declaration. - if (variableDeclarationList.declarations.length >= 1) { - const decl = variableDeclarationList.declarations[0]; - checkVariableDeclaration(decl); + 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, /*errorOutputContainer*/ undefined); } - function checkRightHandSideOfForOf(statement: ForOfStatement): Type { - const use = statement.awaitModifier ? IterationUse.ForAwaitOf : IterationUse.ForOf; - return checkIteratedTypeOrElementType(use, checkNonNullExpression(statement.expression), undefinedType, statement.expression); + function checkBreakOrContinueStatement(node: BreakOrContinueStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) checkGrammarBreakOrContinueStatement(node); + + // TODO: Check that target label is valid } - function checkIteratedTypeOrElementType(use: IterationUse, inputType: Type, sentType: Type, errorNode: Node | undefined): Type { - if (isTypeAny(inputType)) { - return inputType; + function unwrapReturnType(returnType: Type, functionFlags: FunctionFlags) { + const isGenerator = !!(functionFlags & FunctionFlags.Generator); + const isAsync = !!(functionFlags & FunctionFlags.Async); + if (isGenerator) { + const returnIterationType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, isAsync); + if (!returnIterationType) { + return errorType; + } + return isAsync ? getAwaitedTypeNoAlias(unwrapAwaitedType(returnIterationType)) : returnIterationType; } - return getIteratedTypeOrElementType(use, inputType, sentType, errorNode, /*checkAssignability*/ true) || anyType; + return isAsync ? getAwaitedTypeNoAlias(returnType) || errorType : returnType; } - /** - * 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; - } + function isUnwrappedReturnTypeUndefinedVoidOrAny(func: SignatureDeclaration, returnType: Type): boolean { + const type = unwrapReturnType(returnType, getFunctionFlags(func)); + return !!(type && (maybeTypeOfKind(type, TypeFlags.Void) || type.flags & (TypeFlags.Any | TypeFlags.Undefined))); + } - const uplevelIteration = languageVersion >= ScriptTarget.ES2015; - const downlevelIteration = !uplevelIteration && compilerOptions.downlevelIteration; - const possibleOutOfBounds = compilerOptions.noUncheckedIndexedAccess && !!(use & IterationUse.PossiblyOutOfBounds); + function checkReturnStatement(node: ReturnStatement) { + // Grammar checking + if (checkGrammarStatementInAmbientContext(node)) { + return; + } - // 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); - } + const container = getContainingFunctionOrClassStaticBlock(node); + if(container && isClassStaticBlockDeclaration(container)) { + grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_cannot_be_used_inside_a_class_static_block); + return; } - let arrayType = inputType; - let reportedError = false; - let hasStringConstituent = false; + if (!container) { + grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_can_only_be_used_within_a_function_body); + return; + } - // 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 as UnionType).types; - const filteredTypes = filter(arrayTypes, t => !(t.flags & TypeFlags.StringLike)); - if (filteredTypes !== arrayTypes) { - arrayType = getUnionType(filteredTypes, UnionReduction.Subtype); + const signature = getSignatureFromDeclaration(container); + const returnType = getReturnTypeOfSignature(signature); + const functionFlags = getFunctionFlags(container); + if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) { + const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; + if (container.kind === SyntaxKind.SetAccessor) { + if (node.expression) { + error(node, Diagnostics.Setters_cannot_return_a_value); } } - 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; - } + else if (container.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); } - - // 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; + } + else if (getReturnTypeFromAnnotation(container)) { + const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; + const unwrappedExprType = functionFlags & FunctionFlags.Async + ? checkAwaitedType(exprType, /*withAlias*/ false, 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 (container.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeUndefinedVoidOrAny(container, 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); + } + } - 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 allowsStrings = !!(use & IterationUse.AllowsStringInputFlag) && !hasStringConstituent; - const [defaultDiagnostic, maybeMissingAwait] = getIterationDiagnosticDetails(allowsStrings, downlevelIteration); - errorAndMaybeSuggestAwait( - errorNode, - maybeMissingAwait && !!getAwaitedTypeOfPromise(arrayType), - defaultDiagnostic, - typeToString(arrayType)); + 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); } - return hasStringConstituent ? possibleOutOfBounds ? includeUndefinedInIndexSignature(stringType) : stringType : undefined; } - const arrayElementType = getIndexTypeOfType(arrayType, numberType); - 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; - } + checkExpression(node.expression); - return getUnionType(possibleOutOfBounds ? [arrayElementType, stringType, undefinedType] : [arrayElementType, stringType], UnionReduction.Subtype); + 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); } + } - return (use & IterationUse.PossiblyOutOfBounds) ? includeUndefinedInIndexSignature(arrayElementType) : arrayElementType; + function checkSwitchStatement(node: SwitchStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); - function getIterationDiagnosticDetails(allowsStrings: boolean, downlevelIteration: boolean | undefined): [error: DiagnosticMessage, maybeMissingAwait: boolean] { - if (downlevelIteration) { - return allowsStrings - ? [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] - : [Diagnostics.Type_0_is_not_an_array_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator, true]; - } + let firstDefaultClause: CaseOrDefaultClause; + let hasDuplicateDefaultClause = false; - const yieldType = getIterationTypeOfIterable(use, IterationTypeKind.Yield, inputType, /*errorNode*/ undefined); + const expressionType = checkExpression(node.expression); - if (yieldType) { - return [Diagnostics.Type_0_can_only_be_iterated_through_when_using_the_downlevelIteration_flag_or_with_a_target_of_es2015_or_higher, false]; + 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 (isES2015OrLaterIterable(inputType.symbol?.escapedName)) { - return [Diagnostics.Type_0_can_only_be_iterated_through_when_using_the_downlevelIteration_flag_or_with_a_target_of_es2015_or_higher, true]; + if (clause.kind === SyntaxKind.CaseClause) { + addLazyDiagnostic(createLazyCaseClauseDiagnostics(clause)); + } + forEach(clause.statements, checkSourceElement); + if (compilerOptions.noFallthroughCasesInSwitch && clause.fallthroughFlowNode && isReachableFlowNode(clause.fallthroughFlowNode)) { + error(clause, Diagnostics.Fallthrough_case_in_switch); } - return allowsStrings - ? [Diagnostics.Type_0_is_not_an_array_type_or_a_string_type, true] - : [Diagnostics.Type_0_is_not_an_array_type, true]; + function createLazyCaseClauseDiagnostics(clause: CaseClause) { + return () => { + // 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. + const caseType = checkExpression(clause.expression); + + if (!isTypeEqualityComparableTo(expressionType, caseType)) { + // expressionType is not comparable to caseType, try the reversed check and report errors if it fails + checkTypeComparableTo(caseType, expressionType, clause.expression, /*headMessage*/ undefined); + } + }; + } + }); + if (node.caseBlock.locals) { + registerForUnusedIdentifiersCheck(node.caseBlock); } } - function isES2015OrLaterIterable(n: __String) { - switch (n) { - case "Float32Array": - case "Float64Array": - case "Int16Array": - case "Int32Array": - case "Int8Array": - case "NodeList": - case "Uint16Array": - case "Uint32Array": - case "Uint8Array": - case "Uint8ClampedArray": - return true; + function checkLabeledStatement(node: LabeledStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) { + findAncestor(node.parent, current => { + if (isFunctionLike(current)) { + return "quit"; + } + if (current.kind === SyntaxKind.LabeledStatement && (current as LabeledStatement).label.escapedText === node.label.escapedText) { + grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getTextOfNode(node.label)); + return true; + } + return false; + }); } - return false; + + // ensure that label is unique + checkSourceElement(node.statement); } - /** - * 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; + function checkThrowStatement(node: ThrowStatement) { + // Grammar checking + if (!checkGrammarStatementInAmbientContext(node)) { + if (isIdentifier(node.expression) && !node.expression.escapedText) { + grammarErrorAfterFirstToken(node, Diagnostics.Line_break_not_permitted_here); + } } - const iterationTypes = getIterationTypesOfIterable(inputType, use, errorNode); - return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(typeKind)]; + if (node.expression) { + checkExpression(node.expression); + } } - 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. + function checkTryStatement(node: TryStatement) { + // Grammar checking + checkGrammarStatementInAmbientContext(node); - // 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); + checkBlock(node.tryBlock); + const catchClause = node.catchClause; + if (catchClause) { + // Grammar checking + if (catchClause.variableDeclaration) { + const declaration = catchClause.variableDeclaration; + checkVariableLikeDeclaration(declaration); + const typeNode = getEffectiveTypeAnnotationNode(declaration); + if (typeNode) { + const type = getTypeFromTypeNode(typeNode); + if (type && !(type.flags & TypeFlags.AnyOrUnknown)) { + grammarErrorOnFirstToken(typeNode, 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?.valueDeclaration && (blockLocal.flags & SymbolFlags.BlockScopedVariable) !== 0) { + grammarErrorOnNode(blockLocal.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, unescapeLeadingUnderscores(caughtName)); + } + }); + } + } } - return iterationTypes; + + checkBlock(catchClause.block); + } + + if (node.finallyBlock) { + checkBlock(node.finallyBlock); } - 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; + function checkIndexConstraints(type: Type, symbol: Symbol, isStaticIndex?: boolean) { + const indexInfos = getIndexInfosOfType(type); + if (indexInfos.length === 0) { + return; + } + for (const prop of getPropertiesOfObjectType(type)) { + if (!(isStaticIndex && prop.flags & SymbolFlags.Prototype)) { + checkIndexConstraintForProperty(type, prop, getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique, /*includeNonPublic*/ true), getNonMissingTypeOfSymbol(prop)); } - if (iterationTypes === anyIterationTypes) { - return anyIterationTypes; + } + const typeDeclaration = symbol.valueDeclaration; + if (typeDeclaration && isClassLike(typeDeclaration)) { + for (const member of typeDeclaration.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 (!isStatic(member) && !hasBindableName(member)) { + const symbol = getSymbolOfDeclaration(member); + checkIndexConstraintForProperty(type, symbol, getTypeOfExpression((member as DynamicNamedDeclaration).name.expression), getNonMissingTypeOfSymbol(symbol)); + } } - 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)); + if (indexInfos.length > 1) { + for (const info of indexInfos) { + checkIndexConstraintForIndexSignature(type, info); + } } - return noIterationTypes; } - function getCachedIterationTypes(type: Type, cacheKey: MatchingKeys) { - return (type as IterableOrIteratorType)[cacheKey]; + function checkIndexConstraintForProperty(type: Type, prop: Symbol, propNameType: Type, propType: Type) { + const declaration = prop.valueDeclaration; + const name = getNameOfDeclaration(declaration); + if (name && isPrivateIdentifier(name)) { + return; + } + const indexInfos = getApplicableIndexInfos(type, propNameType); + const interfaceDeclaration = getObjectFlags(type) & ObjectFlags.Interface ? getDeclarationOfKind(type.symbol, SyntaxKind.InterfaceDeclaration) : undefined; + const propDeclaration = declaration && declaration.kind === SyntaxKind.BinaryExpression || + name && name.kind === SyntaxKind.ComputedPropertyName ? declaration : undefined; + const localPropDeclaration = getParentOfSymbol(prop) === type.symbol ? declaration : undefined; + for (const info of indexInfos) { + const localIndexDeclaration = info.declaration && getParentOfSymbol(getSymbolOfDeclaration(info.declaration)) === type.symbol ? info.declaration : undefined; + // We check only when (a) the property is declared in the containing type, or (b) the applicable index signature is declared + // in the containing type, or (c) the containing type is an interface and no base interface contains both the property and + // the index signature (i.e. property and index signature are declared in separate inherited interfaces). + const errorNode = localPropDeclaration || localIndexDeclaration || + (interfaceDeclaration && !some(getBaseTypes(type as InterfaceType), base => !!getPropertyOfObjectType(base, prop.escapedName) && !!getIndexTypeOfType(base, info.keyType)) ? interfaceDeclaration : undefined); + if (errorNode && !isTypeAssignableTo(propType, info.type)) { + const diagnostic = createError(errorNode, Diagnostics.Property_0_of_type_1_is_not_assignable_to_2_index_type_3, + symbolToString(prop), typeToString(propType), typeToString(info.keyType), typeToString(info.type)); + if (propDeclaration && errorNode !== propDeclaration) { + addRelatedInfo(diagnostic, createDiagnosticForNode(propDeclaration, Diagnostics._0_is_declared_here, symbolToString(prop))); + } + diagnostics.add(diagnostic); + } + } } - function setCachedIterationTypes(type: Type, cacheKey: MatchingKeys, cachedTypes: IterationTypes) { - return (type as IterableOrIteratorType)[cacheKey] = cachedTypes; + function checkIndexConstraintForIndexSignature(type: Type, checkInfo: IndexInfo) { + const declaration = checkInfo.declaration; + const indexInfos = getApplicableIndexInfos(type, checkInfo.keyType); + const interfaceDeclaration = getObjectFlags(type) & ObjectFlags.Interface ? getDeclarationOfKind(type.symbol, SyntaxKind.InterfaceDeclaration) : undefined; + const localCheckDeclaration = declaration && getParentOfSymbol(getSymbolOfDeclaration(declaration)) === type.symbol ? declaration : undefined; + for (const info of indexInfos) { + if (info === checkInfo) continue; + const localIndexDeclaration = info.declaration && getParentOfSymbol(getSymbolOfDeclaration(info.declaration)) === type.symbol ? info.declaration : undefined; + // We check only when (a) the check index signature is declared in the containing type, or (b) the applicable index + // signature is declared in the containing type, or (c) the containing type is an interface and no base interface contains + // both index signatures (i.e. the index signatures are declared in separate inherited interfaces). + const errorNode = localCheckDeclaration || localIndexDeclaration || + (interfaceDeclaration && !some(getBaseTypes(type as InterfaceType), base => !!getIndexInfoOfType(base, checkInfo.keyType) && !!getIndexTypeOfType(base, info.keyType)) ? interfaceDeclaration : undefined); + if (errorNode && !isTypeAssignableTo(checkInfo.type, info.type)) { + error(errorNode, Diagnostics._0_index_type_1_is_not_assignable_to_2_index_type_3, + typeToString(checkInfo.keyType), typeToString(checkInfo.type), typeToString(info.keyType), typeToString(info.type)); + } + } + } + + 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 "never": + case "number": + case "bigint": + case "boolean": + case "string": + case "symbol": + case "void": + case "object": + error(name, message, name.escapedText as string); + } } /** - * 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. + * The name cannot be used as 'Object' of user defined types with special target. */ - function getIterationTypesOfIterable(type: Type, use: IterationUse, errorNode: Node | undefined) { - if (isTypeAny(type)) { - return anyIterationTypes; + function checkClassNameCollisionWithObject(name: Identifier): void { + if (languageVersion >= ScriptTarget.ES5 && name.escapedText === "Object" + && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(name).impliedNodeFormat === ModuleKind.CommonJS)) { + error(name, Diagnostics.Class_name_cannot_be_Object_when_targeting_ES5_with_module_0, ModuleKind[moduleKind]); // https://github.com/Microsoft/TypeScript/issues/17494 } + } - if (!(type.flags & TypeFlags.Union)) { - const errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined = errorNode ? { errors: undefined } : undefined; - const iterationTypes = getIterationTypesOfIterableWorker(type, use, errorNode, errorOutputContainer); - if (iterationTypes === noIterationTypes) { - if (errorNode) { - const rootDiag = reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); - if (errorOutputContainer?.errors) { - addRelatedInfo(rootDiag, ...errorOutputContainer.errors); - } - } - return undefined; + function checkUnmatchedJSDocParameters(node: SignatureDeclaration) { + const jsdocParameters = filter(getJSDocTags(node), isJSDocParameterTag); + if (!length(jsdocParameters)) return; + + const isJs = isInJSFile(node); + const parameters = new Set<__String>(); + const excludedParameters = new Set(); + forEach(node.parameters, ({ name }, index) => { + if (isIdentifier(name)) { + parameters.add(name.escapedText); } - else if (errorOutputContainer?.errors?.length) { - for (const diag of errorOutputContainer.errors) { - diagnostics.add(diag); - } + if (isBindingPattern(name)) { + excludedParameters.add(index); } - 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 errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined = errorNode ? { errors: undefined } : undefined; - const iterationTypes = getIterationTypesOfIterableWorker(constituent, use, errorNode, errorOutputContainer); - if (iterationTypes === noIterationTypes) { - if (errorNode) { - const rootDiag = reportTypeNotIterableError(errorNode, type, !!(use & IterationUse.AllowsAsyncIterablesFlag)); - if (errorOutputContainer?.errors) { - addRelatedInfo(rootDiag, ...errorOutputContainer.errors); + const containsArguments = containsArgumentsReference(node); + if (containsArguments) { + const lastJSDocParamIndex = jsdocParameters.length - 1; + const lastJSDocParam = jsdocParameters[lastJSDocParamIndex]; + if (isJs && lastJSDocParam && isIdentifier(lastJSDocParam.name) && lastJSDocParam.typeExpression && + lastJSDocParam.typeExpression.type && !parameters.has(lastJSDocParam.name.escapedText) && !excludedParameters.has(lastJSDocParamIndex) && !isArrayType(getTypeFromTypeNode(lastJSDocParam.typeExpression.type))) { + error(lastJSDocParam.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(lastJSDocParam.name)); + } + } + else { + forEach(jsdocParameters, ({ name, isNameFirst }, index) => { + if (excludedParameters.has(index) || isIdentifier(name) && parameters.has(name.escapedText)) { + return; + } + if (isQualifiedName(name)) { + if (isJs) { + error(name, Diagnostics.Qualified_name_0_is_not_allowed_without_a_leading_param_object_1, entityNameToString(name), entityNameToString(name.left)); } } - setCachedIterationTypes(type, cacheKey, noIterationTypes); - return undefined; - } - else if (errorOutputContainer?.errors?.length) { - for (const diag of errorOutputContainer.errors) { - diagnostics.add(diag); + else { + if (!isNameFirst) { + errorOrSuggestion(isJs, name, Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name, idText(name)); + } } - } - - 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; - // if we're requesting diagnostics, report errors for a missing `Awaited`. - if (errorNode) { - getGlobalAwaitedSymbol(/*reportErrors*/ true); + }); } - 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. + * Check each type parameter and check that type parameters have no duplicate type parameter declarations */ - function getIterationTypesOfIterableWorker(type: Type, use: IterationUse, errorNode: Node | undefined, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined) { - if (isTypeAny(type)) { - return anyIterationTypes; - } + function checkTypeParameters(typeParameterDeclarations: readonly TypeParameterDeclaration[] | undefined) { + let seenDefault = false; + if (typeParameterDeclarations) { + for (let i = 0; i < typeParameterDeclarations.length; i++) { + const node = typeParameterDeclarations[i]; + checkTypeParameter(node); - // If we are reporting errors and encounter a cached `noIterationTypes`, we should ignore the cached value and continue as if nothing was cached. - // In addition, we should not cache any new results for this call. - let noCache = false; + addLazyDiagnostic(createCheckTypeParameterDiagnostic(node, i)); + } + } - if (use & IterationUse.AllowsAsyncIterablesFlag) { - const iterationTypes = - getIterationTypesOfIterableCached(type, asyncIterationTypesResolver) || - getIterationTypesOfIterableFast(type, asyncIterationTypesResolver); - if (iterationTypes) { - if (iterationTypes === noIterationTypes && errorNode) { - // ignore the cached value - noCache = true; + function createCheckTypeParameterDiagnostic(node: TypeParameterDeclaration, i: number) { + return () => { + if (node.default) { + seenDefault = true; + checkTypeParametersNotReferenced(node.default, typeParameterDeclarations!, i); } - else { - return use & IterationUse.ForOfFlag ? - getAsyncFromSyncIterationTypes(iterationTypes, errorNode) : - iterationTypes; + 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 as TypeReferenceNode); + if (type.flags & TypeFlags.TypeParameter) { + for (let i = index; i < typeParameters.length; i++) { + if (type.symbol === getSymbolOfDeclaration(typeParameters[i])) { + error(node, Diagnostics.Type_parameter_defaults_can_only_reference_previously_declared_type_parameters); + } + } } } + forEachChild(node, visit); } + } - if (use & IterationUse.AllowsSyncIterablesFlag) { - let iterationTypes = - getIterationTypesOfIterableCached(type, syncIterationTypesResolver) || - getIterationTypesOfIterableFast(type, syncIterationTypesResolver); - if (iterationTypes) { - if (iterationTypes === noIterationTypes && errorNode) { - // ignore the cached value - noCache = true; - } - else { - if (use & IterationUse.AllowsAsyncIterablesFlag) { - // for a sync iterable in an async context, only use the cached types if they are valid. - if (iterationTypes !== noIterationTypes) { - iterationTypes = getAsyncFromSyncIterationTypes(iterationTypes, errorNode); - return noCache ? iterationTypes : setCachedIterationTypes(type, "iterationTypesOfAsyncIterable", iterationTypes); - } - } - else { - return iterationTypes; - } + /** Check that type parameter lists are identical across multiple declarations */ + function checkTypeParameterListsIdentical(symbol: Symbol) { + if (symbol.declarations && symbol.declarations.length === 1) { + return; + } + + const links = getSymbolLinks(symbol); + if (!links.typeParametersChecked) { + links.typeParametersChecked = true; + const declarations = getClassOrInterfaceDeclarationsOfSymbol(symbol); + if (!declarations || declarations.length <= 1) { + return; + } + + const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; + if (!areTypeParametersIdentical(declarations, type.localTypeParameters!, getEffectiveTypeParameterDeclarations)) { + // 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); } } } + } - if (use & IterationUse.AllowsAsyncIterablesFlag) { - const iterationTypes = getIterationTypesOfIterableSlow(type, asyncIterationTypesResolver, errorNode, errorOutputContainer, noCache); - if (iterationTypes !== noIterationTypes) { - return iterationTypes; + function areTypeParametersIdentical(declarations: readonly T[], targetParameters: TypeParameter[], getTypeParameterDeclarations: (node: T) => readonly TypeParameterDeclaration[]) { + 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 = getTypeParameterDeclarations(declaration); + const numTypeParameters = sourceParameters.length; + if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) { + return false; } - } - if (use & IterationUse.AllowsSyncIterablesFlag) { - let iterationTypes = getIterationTypesOfIterableSlow(type, syncIterationTypesResolver, errorNode, errorOutputContainer, noCache); - if (iterationTypes !== noIterationTypes) { - if (use & IterationUse.AllowsAsyncIterablesFlag) { - iterationTypes = getAsyncFromSyncIterationTypes(iterationTypes, errorNode); - return noCache ? iterationTypes : setCachedIterationTypes(type, "iterationTypesOfAsyncIterable", iterationTypes); + 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; } - else { - return iterationTypes; + + // 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 noIterationTypes; + return true; } - /** - * 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 getFirstTransformableStaticClassElement(node: ClassLikeDeclaration) { + const willTransformStaticElementsOfDecoratedClass = + !legacyDecorators && languageVersion < ScriptTarget.ESNext && + classOrConstructorParameterIsDecorated(/*useLegacyDecorators*/ false, node); + const willTransformPrivateElementsOrClassStaticBlocks = languageVersion <= ScriptTarget.ES2022; + const willTransformInitializers = !useDefineForClassFields || languageVersion < ScriptTarget.ES2022; + if (willTransformStaticElementsOfDecoratedClass || willTransformPrivateElementsOrClassStaticBlocks) { + for (const member of node.members) { + if (willTransformStaticElementsOfDecoratedClass && classElementOrClassElementParameterIsDecorated(/*useLegacyDecorators*/ false, member, node)) { + return firstOrUndefined(getDecorators(node)) ?? node; + } + else if (willTransformPrivateElementsOrClassStaticBlocks) { + if (isClassStaticBlockDeclaration(member)) { + return member; + } + else if (isStatic(member)) { + if (isPrivateIdentifierClassElementDeclaration(member) || + willTransformInitializers && isInitializedProperty(member)) { + return member; + } + } + } + } + } } - function getIterationTypesOfGlobalIterableType(globalType: Type, resolver: IterationTypesResolver) { - const globalIterationTypes = - getIterationTypesOfIterableCached(globalType, resolver) || - getIterationTypesOfIterableSlow(globalType, resolver, /*errorNode*/ undefined, /*errorOutputContainer*/ undefined, /*noCache*/ false); - return globalIterationTypes === noIterationTypes ? defaultIterationTypes : globalIterationTypes; - } + function checkClassExpressionExternalHelpers(node: ClassExpression) { + if (node.name) return; - /** - * 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(resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || yieldType, resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || returnType, nextType)); - } + const parent = walkUpOuterExpressions(node); + if (!isNamedEvaluationSource(parent)) return; - // 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(resolver.resolveIterationType(yieldType, /*errorNode*/ undefined) || yieldType, resolver.resolveIterationType(returnType, /*errorNode*/ undefined) || returnType, nextType)); + const willTransformESDecorators = !legacyDecorators && languageVersion < ScriptTarget.ESNext; + let location: Node | undefined; + if (willTransformESDecorators && classOrConstructorParameterIsDecorated(/*useLegacyDecorators*/ false, node)) { + location = firstOrUndefined(getDecorators(node)) ?? node; } - } - - function getPropertyNameForKnownSymbolName(symbolName: string): __String { - const ctorType = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false); - const uniqueType = ctorType && getTypeOfPropertyOfType(getTypeOfSymbol(ctorType), escapeLeadingUnderscores(symbolName)); - return uniqueType && isTypeUsableAsPropertyName(uniqueType) ? getPropertyNameFromType(uniqueType) : `__@${symbolName}` as __String; - } - - /** - * 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, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined, noCache: boolean) { - const method = getPropertyOfType(type, getPropertyNameForKnownSymbolName(resolver.iteratorSymbolName)); - const methodType = method && !(method.flags & SymbolFlags.Optional) ? getTypeOfSymbol(method) : undefined; - if (isTypeAny(methodType)) { - return noCache ? anyIterationTypes : setCachedIterationTypes(type, resolver.iterableCacheKey, anyIterationTypes); + else { + location = getFirstTransformableStaticClassElement(node); } - const signatures = methodType ? getSignaturesOfType(methodType, SignatureKind.Call) : undefined; - if (!some(signatures)) { - return noCache ? noIterationTypes : setCachedIterationTypes(type, resolver.iterableCacheKey, noIterationTypes); + if (location) { + checkExternalEmitHelpers(location, ExternalEmitHelpers.SetFunctionName); + if ((isPropertyAssignment(parent) || isPropertyDeclaration(parent) || isBindingElement(parent)) && isComputedPropertyName(parent.name)) { + checkExternalEmitHelpers(location, ExternalEmitHelpers.PropKey); + } } - - const iteratorType = getIntersectionType(map(signatures, getReturnTypeOfSignature)); - const iterationTypes = getIterationTypesOfIteratorWorker(iteratorType, resolver, errorNode, errorOutputContainer, noCache) ?? noIterationTypes; - return noCache ? iterationTypes : setCachedIterationTypes(type, resolver.iterableCacheKey, iterationTypes); } - function reportTypeNotIterableError(errorNode: Node, type: Type, allowAsyncIterables: boolean): Diagnostic { - 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; - const suggestAwait = - // for (const x of Promise<...>) or [...Promise<...>] - !!getAwaitedTypeOfPromise(type) - // for (const x of AsyncIterable<...>) - || ( - !allowAsyncIterables && - isForOfStatement(errorNode.parent) && - errorNode.parent.expression === errorNode && - getGlobalAsyncIterableType(/*reportErrors*/ false) !== emptyGenericType && - isTypeAssignableTo(type, getGlobalAsyncIterableType(/*reportErrors*/ false) - )); - return errorAndMaybeSuggestAwait(errorNode, suggestAwait, message, typeToString(type)); + function checkClassExpression(node: ClassExpression): Type { + checkClassLikeDeclaration(node); + checkNodeDeferred(node); + checkClassExpressionExternalHelpers(node); + return getTypeOfSymbol(getSymbolOfDeclaration(node)); } - /** - * 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, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined) { - return getIterationTypesOfIteratorWorker(type, resolver, errorNode, errorOutputContainer, /*noCache*/ false); + function checkClassExpressionDeferred(node: ClassExpression) { + forEach(node.members, checkSourceElement); + registerForUnusedIdentifiersCheck(node); } - /** - * 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. - * - * NOTE: You probably don't want to call this directly and should be calling - * `getIterationTypesOfIterator` instead. - */ - function getIterationTypesOfIteratorWorker(type: Type, resolver: IterationTypesResolver, errorNode: Node | undefined, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined, noCache: boolean) { - if (isTypeAny(type)) { - return anyIterationTypes; + function checkClassDeclaration(node: ClassDeclaration) { + const firstDecorator = find(node.modifiers, isDecorator); + if (legacyDecorators && firstDecorator && some(node.members, p => hasStaticModifier(p) && isPrivateIdentifierClassElementDeclaration(p))) { + grammarErrorOnNode(firstDecorator, Diagnostics.Class_decorators_can_t_be_used_with_static_private_identifier_Consider_removing_the_experimental_decorator); } - - let iterationTypes = - getIterationTypesOfIteratorCached(type, resolver) || - getIterationTypesOfIteratorFast(type, resolver); - - if (iterationTypes === noIterationTypes && errorNode) { - iterationTypes = undefined; - noCache = true; + 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); - iterationTypes ??= getIterationTypesOfIteratorSlow(type, resolver, errorNode, errorOutputContainer, noCache); - return iterationTypes === noIterationTypes ? undefined : iterationTypes; + registerForUnusedIdentifiersCheck(node); } - /** - * 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); - } + function checkClassLikeDeclaration(node: ClassLikeDeclaration) { + checkGrammarClassLikeDeclaration(node); + checkDecorators(node); + checkCollisionsForDeclarationName(node, node.name); + checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); + checkExportsOnMergedDeclarations(node); + const symbol = getSymbolOfDeclaration(node); + const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; + const typeWithThis = getTypeWithThisArgument(type); + const staticType = getTypeOfSymbol(symbol) as ObjectType; + checkTypeParameterListsIdentical(symbol); + checkFunctionOrConstructorSymbol(symbol); + checkClassForDuplicateDeclarations(node); - /** - * 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, /*errorOutputContainer*/ undefined, /*noCache*/ false); - 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)); + // Only check for reserved static identifiers on non-ambient context. + const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient); + if (!nodeInAmbientContext) { + checkClassForStaticPropertyNameConflicts(node); } - } - - 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); - } + 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); + } - function isReturnIteratorResult(type: Type) { - return isIteratorResult(type, IterationTypeKind.Return); - } + const baseTypes = getBaseTypes(type); + if (baseTypes.length) { + addLazyDiagnostic(() => { + 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) { + if (!isMixinConstructorType(staticType)) { + error(node.name || node, Diagnostics.A_mixin_class_must_have_a_constructor_with_a_single_rest_parameter_of_type_any); + } + else { + const constructSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); + if (constructSignatures.some(signature => signature.flags & SignatureFlags.Abstract) && !hasSyntacticModifier(node, ModifierFlags.Abstract)) { + error(node.name || node, Diagnostics.A_mixin_class_that_extends_from_a_type_variable_containing_an_abstract_construct_signature_must_also_be_declared_abstract); + } + } + } - /** - * 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; + 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 cachedTypes = getCachedIterationTypes(type, "iterationTypesOfIteratorResult"); - if (cachedTypes) { - return cachedTypes; - } + checkMembersForOverrideModifier(node, type, typeWithThis, staticType); - // 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)); + const implementedTypeNodes = getEffectiveImplementsTypeNodes(node); + if (implementedTypeNodes) { + for (const typeRefNode of implementedTypeNodes) { + if (!isEntityNameExpression(typeRefNode.expression) || isOptionalChain(typeRefNode.expression)) { + error(typeRefNode.expression, Diagnostics.A_class_can_only_implement_an_identifier_Slashqualified_name_with_optional_type_arguments); + } + checkTypeReferenceNode(typeRefNode); + addLazyDiagnostic(createImplementsDiagnostics(typeRefNode)); + } } - if (isReferenceToType(type, getGlobalIteratorReturnResultType(/*reportErrors*/ false))) { - const returnType = getTypeArguments(type as GenericType)[0]; - return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", createIterationTypes(/*yieldType*/ undefined, returnType, /*nextType*/ undefined)); + + addLazyDiagnostic(() => { + checkIndexConstraints(type, symbol); + checkIndexConstraints(staticType, symbol, /*isStaticIndex*/ true); + checkTypeForDuplicateIndexSignatures(node); + checkPropertyInitialization(node); + }); + + function createImplementsDiagnostics(typeRefNode: ExpressionWithTypeArguments) { + return () => { + const t = getReducedType(getTypeFromTypeNode(typeRefNode)); + if (!isErrorType(t)) { + 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); + } + } + }; } + } - // 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; + function checkMembersForOverrideModifier(node: ClassLikeDeclaration, type: InterfaceType, typeWithThis: Type, staticType: ObjectType) { + const baseTypeNode = getEffectiveBaseTypeNode(node); + const baseTypes = baseTypeNode && getBaseTypes(type); + const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined; + const baseStaticType = getBaseConstructorTypeOfClass(type); - const returnIteratorResult = filterType(type, isReturnIteratorResult); - const returnType = returnIteratorResult !== neverType ? getTypeOfPropertyOfType(returnIteratorResult, "value" as __String) : undefined; + for (const member of node.members) { + if (hasAmbientModifier(member)) { + continue; + } - if (!yieldType && !returnType) { - return setCachedIterationTypes(type, "iterationTypesOfIteratorResult", noIterationTypes); + if (isConstructorDeclaration(member)) { + forEach(member.parameters, param => { + if (isParameterPropertyDeclaration(param, member)) { + checkExistingMemberForOverrideModifier( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + param, + /*memberIsParameterProperty*/ true + ); + } + }); + } + checkExistingMemberForOverrideModifier( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + member, + /*memberIsParameterProperty*/ false, + ); } - - // 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`. + * @param member Existing member node to be checked. + * Note: `member` cannot be a synthetic node. */ - function getIterationTypesOfMethod(type: Type, resolver: IterationTypesResolver, methodName: "next" | "return" | "throw", errorNode: Node | undefined, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined): IterationTypes | undefined { - const method = getPropertyOfType(type, methodName as __String); - - // Ignore 'return' or 'throw' if they are missing. - if (!method && methodName !== "next") { - return undefined; + function checkExistingMemberForOverrideModifier( + node: ClassLikeDeclaration, + staticType: ObjectType, + baseStaticType: Type, + baseWithThis: Type | undefined, + type: InterfaceType, + typeWithThis: Type, + member: ClassElement | ParameterPropertyDeclaration, + memberIsParameterProperty: boolean, + reportErrors = true, + ): MemberOverrideStatus { + const declaredProp = member.name + && getSymbolAtLocation(member.name) + || getSymbolAtLocation(member); + if (!declaredProp) { + return MemberOverrideStatus.Ok; } - const methodType = method && !(methodName === "next" && (method.flags & SymbolFlags.Optional)) - ? methodName === "next" ? getTypeOfSymbol(method) : getTypeWithFacts(getTypeOfSymbol(method), TypeFacts.NEUndefinedOrNull) - : undefined; + return checkMemberForOverrideModifier( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + hasOverrideModifier(member), + hasAbstractModifier(member), + isStatic(member), + memberIsParameterProperty, + symbolName(declaredProp), + reportErrors ? member : undefined, + ); + } - if (isTypeAny(methodType)) { - // `return()` and `throw()` don't provide a *next* type. - return methodName === "next" ? anyIterationTypes : anyIterationTypesExceptNext; - } + /** + * Checks a class member declaration for either a missing or an invalid `override` modifier. + * Note: this function can be used for speculative checking, + * i.e. checking a member that does not yet exist in the program. + * An example of that would be to call this function in a completions scenario, + * when offering a method declaration as completion. + * @param errorNode The node where we should report an error, or undefined if we should not report errors. + */ + function checkMemberForOverrideModifier( + node: ClassLikeDeclaration, + staticType: ObjectType, + baseStaticType: Type, + baseWithThis: Type | undefined, + type: InterfaceType, + typeWithThis: Type, + memberHasOverrideModifier: boolean, + memberHasAbstractModifier: boolean, + memberIsStatic: boolean, + memberIsParameterProperty: boolean, + memberName: string, + errorNode?: Node, + ): MemberOverrideStatus { + const isJs = isInJSFile(node); + const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient); + if (baseWithThis && (memberHasOverrideModifier || compilerOptions.noImplicitOverride)) { + const memberEscapedName = escapeLeadingUnderscores(memberName); + const thisType = memberIsStatic ? staticType : typeWithThis; + const baseType = memberIsStatic ? baseStaticType : baseWithThis; + const prop = getPropertyOfType(thisType, memberEscapedName); + const baseProp = getPropertyOfType(baseType, memberEscapedName); - // 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; - if (errorOutputContainer) { - errorOutputContainer.errors ??= []; - errorOutputContainer.errors.push(createDiagnosticForNode(errorNode, diagnostic, methodName)); - } - else { - error(errorNode, diagnostic, methodName); + const baseClassName = typeToString(baseWithThis); + if (prop && !baseProp && memberHasOverrideModifier) { + if (errorNode) { + const suggestion = getSuggestedSymbolForNonexistentClassMember(memberName, baseType); // Again, using symbol name: note that's different from `symbol.escapedName` + suggestion ? + error( + errorNode, + isJs ? + Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1 : + Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1, + baseClassName, + symbolToString(suggestion)) : + error( + errorNode, + isJs ? + Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0 : + Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0, + baseClassName); } + return MemberOverrideStatus.HasInvalidOverride; } - return methodName === "next" ? noIterationTypes : undefined; - } + else if (prop && baseProp?.declarations && compilerOptions.noImplicitOverride && !nodeInAmbientContext) { + const baseHasAbstract = some(baseProp.declarations, hasAbstractModifier); + if (memberHasOverrideModifier) { + return MemberOverrideStatus.Ok; + } - // 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); + if (!baseHasAbstract) { + if (errorNode) { + const diag = memberIsParameterProperty ? + isJs ? + Diagnostics.This_parameter_property_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0 : + Diagnostics.This_parameter_property_must_have_an_override_modifier_because_it_overrides_a_member_in_base_class_0 : + isJs ? + Diagnostics.This_member_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0 : + Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0; + error(errorNode, diag, baseClassName); + } + return MemberOverrideStatus.NeedsOverride; + } + else if (memberHasAbstractModifier && baseHasAbstract) { + if (errorNode) { + error(errorNode, Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0, baseClassName); + } + return MemberOverrideStatus.NeedsOverride; + } } } - - // 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)); + else if (memberHasOverrideModifier) { + if (errorNode) { + const className = typeToString(type); + error( + errorNode, + isJs ? + Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_containing_class_0_does_not_extend_another_class : + Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class, + className); } - methodReturnTypes = append(methodReturnTypes, getReturnTypeOfSignature(signature)); + return MemberOverrideStatus.HasInvalidOverride; } - // 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); - } - } + return MemberOverrideStatus.Ok; + } - // 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) { - if (errorOutputContainer) { - errorOutputContainer.errors ??= []; - errorOutputContainer.errors.push(createDiagnosticForNode(errorNode, resolver.mustHaveAValueDiagnostic, methodName)); - } - else { - error(errorNode, resolver.mustHaveAValueDiagnostic, methodName); + 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 (isStatic(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, /*headMessage*/ undefined, rootChain)) { + issuedMemberError = true; + } } } - yieldType = anyType; - returnTypes = append(returnTypes, anyType); } - else { - yieldType = iterationTypes.yieldType; - returnTypes = append(returnTypes, iterationTypes.returnType); + if (!issuedMemberError) { + // check again with diagnostics to generate a less-specific error + checkTypeAssignableTo(typeWithThis, baseWithThis, node.name || node, broadDiag); } - - 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, errorOutputContainer: { errors: Diagnostic[] | undefined } | undefined, noCache: boolean) { - const iterationTypes = combineIterationTypes([ - getIterationTypesOfMethod(type, resolver, "next", errorNode, errorOutputContainer), - getIterationTypesOfMethod(type, resolver, "return", errorNode, errorOutputContainer), - getIterationTypesOfMethod(type, resolver, "throw", errorNode, errorOutputContainer), - ]); - return noCache ? iterationTypes : setCachedIterationTypes(type, resolver.iteratorCacheKey, iterationTypes); + 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)); + } + } + } } /** - * 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). + * Checks a member declaration node to see if has a missing or invalid `override` modifier. + * @param node Class-like node where the member is declared. + * @param member Member declaration node. + * @param memberSymbol Member symbol. + * Note: `member` can be a synthetic node without a parent. */ - function getIterationTypeOfGeneratorFunctionReturnType(kind: IterationTypeKind, returnType: Type, isAsyncGenerator: boolean): Type | undefined { - if (isTypeAny(returnType)) { - return undefined; + function getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement, memberSymbol: Symbol): MemberOverrideStatus { + if (!member.name) { + return MemberOverrideStatus.Ok; } - const iterationTypes = getIterationTypesOfGeneratorFunctionReturnType(returnType, isAsyncGenerator); - return iterationTypes && iterationTypes[getIterationTypesKeyFromIterationTypeKind(kind)]; - } + const classSymbol = getSymbolOfDeclaration(node); + const type = getDeclaredTypeOfSymbol(classSymbol) as InterfaceType; + const typeWithThis = getTypeWithThisArgument(type); + const staticType = getTypeOfSymbol(classSymbol) as ObjectType; - function getIterationTypesOfGeneratorFunctionReturnType(type: Type, isAsyncGenerator: boolean) { - if (isTypeAny(type)) { - return anyIterationTypes; - } + const baseTypeNode = getEffectiveBaseTypeNode(node); + const baseTypes = baseTypeNode && getBaseTypes(type); + const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined; + const baseStaticType = getBaseConstructorTypeOfClass(type); - const use = isAsyncGenerator ? IterationUse.AsyncGeneratorReturnType : IterationUse.GeneratorReturnType; - const resolver = isAsyncGenerator ? asyncIterationTypesResolver : syncIterationTypesResolver; - return getIterationTypesOfIterable(type, use, /*errorNode*/ undefined) || - getIterationTypesOfIterator(type, resolver, /*errorNode*/ undefined, /*errorOutputContainer*/ undefined); + const memberHasOverrideModifier = member.parent + ? hasOverrideModifier(member) + : hasSyntacticModifier(member, ModifierFlags.Override); + + return checkMemberForOverrideModifier( + node, + staticType, + baseStaticType, + baseWithThis, + type, + typeWithThis, + memberHasOverrideModifier, + hasAbstractModifier(member), + isStatic(member), + /*memberIsParameterProperty*/ false, + symbolName(memberSymbol), + ); } - function checkBreakOrContinueStatement(node: BreakOrContinueStatement) { - // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) checkGrammarBreakOrContinueStatement(node); + 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 + // NOTE: cast to TransientSymbol should be safe because only TransientSymbols have CheckFlags.Instantiated + return getCheckFlags(s) & CheckFlags.Instantiated ? (s as TransientSymbol).links.target! : s; + } - // TODO: Check that target label is valid + function getClassOrInterfaceDeclarationsOfSymbol(symbol: Symbol) { + return filter(symbol.declarations, (d: Declaration): d is ClassDeclaration | InterfaceDeclaration => + d.kind === SyntaxKind.ClassDeclaration || d.kind === SyntaxKind.InterfaceDeclaration); } - function unwrapReturnType(returnType: Type, functionFlags: FunctionFlags) { - const isGenerator = !!(functionFlags & FunctionFlags.Generator); - const isAsync = !!(functionFlags & FunctionFlags.Async); - if (isGenerator) { - const returnIterationType = getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, isAsync); - if (!returnIterationType) { - return errorType; + 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); + let inheritedAbstractMemberNotImplementedError: Diagnostic | undefined; + basePropertyCheck: for (const baseProperty of baseProperties) { + const base = getTargetSymbol(baseProperty); + + if (base.flags & SymbolFlags.Prototype) { + continue; } - return isAsync ? getAwaitedTypeNoAlias(unwrapAwaitedType(returnIterationType)) : returnIterationType; - } - return isAsync ? getAwaitedTypeNoAlias(returnType) || errorType : returnType; - } + const baseSymbol = getPropertyOfObjectType(type, base.escapedName); + if (!baseSymbol) { + continue; + } + const derived = getTargetSymbol(baseSymbol); + const baseDeclarationFlags = getDeclarationModifierFlagsFromSymbol(base); - function isUnwrappedReturnTypeUndefinedVoidOrAny(func: SignatureDeclaration, returnType: Type): boolean { - const type = unwrapReturnType(returnType, getFunctionFlags(func)); - return !!(type && (maybeTypeOfKind(type, TypeFlags.Void) || type.flags & (TypeFlags.Any | TypeFlags.Undefined))); - } + Debug.assert(!!derived, "derived should point to something, even if it is the base class' declaration."); - function checkReturnStatement(node: ReturnStatement) { - // Grammar checking - if (checkGrammarStatementInAmbientContext(node)) { - return; - } + // 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)!; - const container = getContainingFunctionOrClassStaticBlock(node); - if(container && isClassStaticBlockDeclaration(container)) { - grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_cannot_be_used_inside_a_class_static_block); - return; - } + // 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 (!container) { - grammarErrorOnFirstToken(node, Diagnostics.A_return_statement_can_only_be_used_within_a_function_body); - return; - } + if (!inheritedAbstractMemberNotImplementedError) { + inheritedAbstractMemberNotImplementedError = error( + derivedClassDecl, + Diagnostics.Non_abstract_class_0_does_not_implement_all_abstract_members_of_1, + typeToString(type), typeToString(baseType)); - const signature = getSignatureFromDeclaration(container); - const returnType = getReturnTypeOfSignature(signature); - const functionFlags = getFunctionFlags(container); - if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) { - const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; - if (container.kind === SyntaxKind.SetAccessor) { - if (node.expression) { - error(node, Diagnostics.Setters_cannot_return_a_value); + } + if (derivedClassDecl.kind === SyntaxKind.ClassExpression) { + addRelatedInfo( + inheritedAbstractMemberNotImplementedError, + createDiagnosticForNode( + baseProperty.valueDeclaration ?? (baseProperty.declarations && first(baseProperty.declarations)) ?? derivedClassDecl, + Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1, + symbolToString(baseProperty), typeToString(baseType))); + } + else { + addRelatedInfo( + inheritedAbstractMemberNotImplementedError, + createDiagnosticForNode( + baseProperty.valueDeclaration ?? (baseProperty.declarations && first(baseProperty.declarations)) ?? derivedClassDecl, + Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2, + typeToString(type), symbolToString(baseProperty), typeToString(baseType))); + } } } - else if (container.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 { + // 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 ((getCheckFlags(base) & CheckFlags.Synthetic + ? base.declarations?.some(d => isPropertyAbstractOrInterface(d, baseDeclarationFlags)) + : base.declarations?.every(d => isPropertyAbstractOrInterface(d, baseDeclarationFlags))) + || getCheckFlags(base) & CheckFlags.Mapped + || derived.valueDeclaration && isBinaryExpression(derived.valueDeclaration)) { + // when the base property is abstract or from an interface, base/derived flags don't need to match + // for intersection properties, this must be true of *any* of the declarations, for others it must be true of *all* + // 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 (useDefineForClassFields) { + const uninitialized = derived.declarations?.find(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 (getReturnTypeFromAnnotation(container)) { - const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; - const unwrappedExprType = functionFlags & FunctionFlags.Async - ? checkAwaitedType(exprType, /*withAlias*/ false, 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 (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)); } } - else if (container.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeUndefinedVoidOrAny(container, 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); - } - } + function isPropertyAbstractOrInterface(declaration: Declaration, baseDeclarationFlags: ModifierFlags) { + return baseDeclarationFlags & ModifierFlags.Abstract && (!isPropertyDeclaration(declaration) || !declaration.initializer) + || isInterfaceDeclaration(declaration.parent); + } - checkExpression(node.expression); + function getNonInheritedProperties(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); + }); - 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); + 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 && prop.parent === existing.parent) { + seen.delete(prop.escapedName); + } + } } - } - function checkSwitchStatement(node: SwitchStatement) { - // Grammar checking - checkGrammarStatementInAmbientContext(node); + return arrayFrom(seen.values()); + } - let firstDefaultClause: CaseOrDefaultClause; - let hasDuplicateDefaultClause = false; + function checkInheritedPropertiesAreIdentical(type: InterfaceType, typeNode: Node): boolean { + const baseTypes = getBaseTypes(type); + if (baseTypes.length < 2) { + return true; + } - const expressionType = checkExpression(node.expression); + 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; - 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; + 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 { - grammarErrorOnNode(clause, Diagnostics.A_default_clause_cannot_appear_more_than_once_in_a_switch_statement); - hasDuplicateDefaultClause = true; - } - } - - if (clause.kind === SyntaxKind.CaseClause) { - addLazyDiagnostic(createLazyCaseClauseDiagnostics(clause)); - } - forEach(clause.statements, checkSourceElement); - if (compilerOptions.noFallthroughCasesInSwitch && clause.fallthroughFlowNode && isReachableFlowNode(clause.fallthroughFlowNode)) { - error(clause, Diagnostics.Fallthrough_case_in_switch); - } + const isInheritedProperty = existing.containingType !== type; + if (isInheritedProperty && !isPropertyIdenticalTo(existing.prop, prop)) { + ok = false; - function createLazyCaseClauseDiagnostics(clause: CaseClause) { - return () => { - // 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. - const caseType = checkExpression(clause.expression); + const typeName1 = typeToString(existing.containingType); + const typeName2 = typeToString(base); - if (!isTypeEqualityComparableTo(expressionType, caseType)) { - // expressionType is not comparable to caseType, try the reversed check and report errors if it fails - checkTypeComparableTo(caseType, expressionType, clause.expression, /*headMessage*/ undefined); + 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(getSourceFileOfNode(typeNode), typeNode, errorInfo)); } - }; + } } - }); - if (node.caseBlock.locals) { - registerForUnusedIdentifiersCheck(node.caseBlock); } + + return ok; } - function checkLabeledStatement(node: LabeledStatement) { - // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) { - findAncestor(node.parent, current => { - if (isFunctionLike(current)) { - return "quit"; - } - if (current.kind === SyntaxKind.LabeledStatement && (current as LabeledStatement).label.escapedText === node.label.escapedText) { - grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getTextOfNode(node.label)); - return true; + 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 (!isStatic(member) && isPropertyWithoutInitializer(member)) { + const propName = (member as PropertyDeclaration).name; + if (isIdentifier(propName) || isPrivateIdentifier(propName) || isComputedPropertyName(propName)) { + const type = getTypeOfSymbol(getSymbolOfDeclaration(member)); + if (!(type.flags & TypeFlags.AnyOrUnknown || containsUndefinedType(type))) { + 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)); + } + } } - return false; - }); + } } + } - // ensure that label is unique - checkSourceElement(node.statement); + function isPropertyWithoutInitializer(node: Node) { + return node.kind === SyntaxKind.PropertyDeclaration && + !hasAbstractModifier(node) && + !(node as PropertyDeclaration).exclamationToken && + !(node as PropertyDeclaration).initializer; } - function checkThrowStatement(node: ThrowStatement) { - // Grammar checking - if (!checkGrammarStatementInAmbientContext(node)) { - if (isIdentifier(node.expression) && !node.expression.escapedText) { - grammarErrorAfterFirstToken(node, Diagnostics.Line_break_not_permitted_here); + function isPropertyInitializedInStaticBlocks(propName: Identifier | PrivateIdentifier, propType: Type, staticBlocks: readonly ClassStaticBlockDeclaration[], startPos: number, endPos: number) { + for (const staticBlock of staticBlocks) { + // static block must be within the provided range as they are evaluated in document order (unlike constructors) + if (staticBlock.pos >= startPos && staticBlock.pos <= endPos) { + const reference = factory.createPropertyAccessExpression(factory.createThis(), propName); + setParent(reference.expression, reference); + setParent(reference, staticBlock); + reference.flowNode = staticBlock.returnFlowNode; + const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); + if (!containsUndefinedType(flowType)) { + return true; + } } } + return false; + } - if (node.expression) { - checkExpression(node.expression); - } + function isPropertyInitializedInConstructor(propName: Identifier | PrivateIdentifier | ComputedPropertyName, propType: Type, constructor: ConstructorDeclaration) { + const reference = isComputedPropertyName(propName) + ? factory.createElementAccessExpression(factory.createThis(), propName.expression) + : factory.createPropertyAccessExpression(factory.createThis(), propName); + setParent(reference.expression, reference); + setParent(reference, constructor); + reference.flowNode = constructor.returnFlowNode; + const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); + return !containsUndefinedType(flowType); } - function checkTryStatement(node: TryStatement) { + + function checkInterfaceDeclaration(node: InterfaceDeclaration) { // Grammar checking - checkGrammarStatementInAmbientContext(node); + if (!checkGrammarModifiers(node)) checkGrammarInterfaceDeclaration(node); - checkBlock(node.tryBlock); - const catchClause = node.catchClause; - if (catchClause) { - // Grammar checking - if (catchClause.variableDeclaration) { - const declaration = catchClause.variableDeclaration; - checkVariableLikeDeclaration(declaration); - const typeNode = getEffectiveTypeAnnotationNode(declaration); - if (typeNode) { - const type = getTypeFromTypeNode(typeNode); - if (type && !(type.flags & TypeFlags.AnyOrUnknown)) { - grammarErrorOnFirstToken(typeNode, 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?.valueDeclaration && (blockLocal.flags & SymbolFlags.BlockScopedVariable) !== 0) { - grammarErrorOnNode(blockLocal.valueDeclaration, Diagnostics.Cannot_redeclare_identifier_0_in_catch_clause, unescapeLeadingUnderscores(caughtName)); - } - }); + checkTypeParameters(node.typeParameters); + addLazyDiagnostic(() => { + checkTypeNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0); + + checkExportsOnMergedDeclarations(node); + const symbol = getSymbolOfDeclaration(node); + checkTypeParameterListsIdentical(symbol); + + // Only check this symbol once + const firstInterfaceDecl = getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration); + if (node === firstInterfaceDecl) { + const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; + 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, symbol); } } + checkObjectTypeForDuplicateDeclarations(node); + }); + forEach(getInterfaceBaseTypeNodes(node), heritageElement => { + if (!isEntityNameExpression(heritageElement.expression) || isOptionalChain(heritageElement.expression)) { + error(heritageElement.expression, Diagnostics.An_interface_can_only_extend_an_identifier_Slashqualified_name_with_optional_type_arguments); + } + checkTypeReferenceNode(heritageElement); + }); - checkBlock(catchClause.block); - } + forEach(node.members, checkSourceElement); - if (node.finallyBlock) { - checkBlock(node.finallyBlock); - } + addLazyDiagnostic(() => { + checkTypeForDuplicateIndexSignatures(node); + registerForUnusedIdentifiersCheck(node); + }); } - function checkIndexConstraints(type: Type, symbol: Symbol, isStaticIndex?: boolean) { - const indexInfos = getIndexInfosOfType(type); - if (indexInfos.length === 0) { - return; - } - for (const prop of getPropertiesOfObjectType(type)) { - if (!(isStaticIndex && prop.flags & SymbolFlags.Prototype)) { - checkIndexConstraintForProperty(type, prop, getLiteralTypeFromProperty(prop, TypeFlags.StringOrNumberLiteralOrUnique, /*includeNonPublic*/ true), getNonMissingTypeOfSymbol(prop)); + function checkTypeAliasDeclaration(node: TypeAliasDeclaration) { + // Grammar checking + checkGrammarModifiers(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); } } - const typeDeclaration = symbol.valueDeclaration; - if (typeDeclaration && isClassLike(typeDeclaration)) { - for (const member of typeDeclaration.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 (!isStatic(member) && !hasBindableName(member)) { - const symbol = getSymbolOfDeclaration(member); - checkIndexConstraintForProperty(type, symbol, getTypeOfExpression((member as DynamicNamedDeclaration).name.expression), getNonMissingTypeOfSymbol(symbol)); - } - } + else { + checkSourceElement(node.type); + registerForUnusedIdentifiersCheck(node); } - if (indexInfos.length > 1) { - for (const info of indexInfos) { - checkIndexConstraintForIndexSignature(type, info); + } + + 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 checkIndexConstraintForProperty(type: Type, prop: Symbol, propNameType: Type, propType: Type) { - const declaration = prop.valueDeclaration; - const name = getNameOfDeclaration(declaration); - if (name && isPrivateIdentifier(name)) { - return; + function computeMemberValue(member: EnumMember, autoValue: number | undefined) { + if (isComputedNonLiteralName(member.name)) { + error(member.name, Diagnostics.Computed_property_names_are_not_allowed_in_enums); } - const indexInfos = getApplicableIndexInfos(type, propNameType); - const interfaceDeclaration = getObjectFlags(type) & ObjectFlags.Interface ? getDeclarationOfKind(type.symbol, SyntaxKind.InterfaceDeclaration) : undefined; - const propDeclaration = declaration && declaration.kind === SyntaxKind.BinaryExpression || - name && name.kind === SyntaxKind.ComputedPropertyName ? declaration : undefined; - const localPropDeclaration = getParentOfSymbol(prop) === type.symbol ? declaration : undefined; - for (const info of indexInfos) { - const localIndexDeclaration = info.declaration && getParentOfSymbol(getSymbolOfDeclaration(info.declaration)) === type.symbol ? info.declaration : undefined; - // We check only when (a) the property is declared in the containing type, or (b) the applicable index signature is declared - // in the containing type, or (c) the containing type is an interface and no base interface contains both the property and - // the index signature (i.e. property and index signature are declared in separate inherited interfaces). - const errorNode = localPropDeclaration || localIndexDeclaration || - (interfaceDeclaration && !some(getBaseTypes(type as InterfaceType), base => !!getPropertyOfObjectType(base, prop.escapedName) && !!getIndexTypeOfType(base, info.keyType)) ? interfaceDeclaration : undefined); - if (errorNode && !isTypeAssignableTo(propType, info.type)) { - const diagnostic = createError(errorNode, Diagnostics.Property_0_of_type_1_is_not_assignable_to_2_index_type_3, - symbolToString(prop), typeToString(propType), typeToString(info.keyType), typeToString(info.type)); - if (propDeclaration && errorNode !== propDeclaration) { - addRelatedInfo(diagnostic, createDiagnosticForNode(propDeclaration, Diagnostics._0_is_declared_here, symbolToString(prop))); - } - diagnostics.add(diagnostic); + 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)) { + 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 checkIndexConstraintForIndexSignature(type: Type, checkInfo: IndexInfo) { - const declaration = checkInfo.declaration; - const indexInfos = getApplicableIndexInfos(type, checkInfo.keyType); - const interfaceDeclaration = getObjectFlags(type) & ObjectFlags.Interface ? getDeclarationOfKind(type.symbol, SyntaxKind.InterfaceDeclaration) : undefined; - const localCheckDeclaration = declaration && getParentOfSymbol(getSymbolOfDeclaration(declaration)) === type.symbol ? declaration : undefined; - for (const info of indexInfos) { - if (info === checkInfo) continue; - const localIndexDeclaration = info.declaration && getParentOfSymbol(getSymbolOfDeclaration(info.declaration)) === type.symbol ? info.declaration : undefined; - // We check only when (a) the check index signature is declared in the containing type, or (b) the applicable index - // signature is declared in the containing type, or (c) the containing type is an interface and no base interface contains - // both index signatures (i.e. the index signatures are declared in separate inherited interfaces). - const errorNode = localCheckDeclaration || localIndexDeclaration || - (interfaceDeclaration && !some(getBaseTypes(type as InterfaceType), base => !!getIndexInfoOfType(base, checkInfo.keyType) && !!getIndexTypeOfType(base, info.keyType)) ? interfaceDeclaration : undefined); - if (errorNode && !isTypeAssignableTo(checkInfo.type, info.type)) { - error(errorNode, Diagnostics._0_index_type_1_is_not_assignable_to_2_index_type_3, - typeToString(checkInfo.keyType), typeToString(checkInfo.type), typeToString(info.keyType), typeToString(info.type)); + function computeConstantValue(member: EnumMember): string | number | undefined { + const isConstEnum = isEnumConst(member.parent); + const initializer = member.initializer!; + const value = evaluate(initializer, member); + 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 (isConstEnum) { + error(initializer, Diagnostics.const_enum_member_initializers_must_be_constant_expressions); + } + else if (member.parent.flags & NodeFlags.Ambient) { + error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression); + } + else { + checkTypeAssignableTo(checkExpression(initializer), numberType, initializer, Diagnostics.Type_0_is_not_assignable_to_type_1_as_required_for_computed_enum_member_values); + } + return value; } - 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 "never": - case "number": - case "bigint": - case "boolean": - case "string": - case "symbol": - case "void": - case "object": - error(name, message, name.escapedText as string); + function evaluate(expr: Expression, location: Declaration): string | number | undefined { + switch (expr.kind) { + case SyntaxKind.PrefixUnaryExpression: + const value = evaluate((expr as PrefixUnaryExpression).operand, location); + if (typeof value === "number") { + switch ((expr as PrefixUnaryExpression).operator) { + case SyntaxKind.PlusToken: return value; + case SyntaxKind.MinusToken: return -value; + case SyntaxKind.TildeToken: return ~value; + } + } + break; + case SyntaxKind.BinaryExpression: + const left = evaluate((expr as BinaryExpression).left, location); + const right = evaluate((expr as BinaryExpression).right, location); + if (typeof left === "number" && typeof right === "number") { + switch ((expr as BinaryExpression).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 left === "number") && + (typeof right === "string" || typeof right === "number") && + (expr as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken) { + return "" + left + right; + } + break; + case SyntaxKind.StringLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + return (expr as StringLiteralLike).text; + case SyntaxKind.TemplateExpression: + return evaluateTemplateExpression(expr as TemplateExpression, location); + case SyntaxKind.NumericLiteral: + checkGrammarNumericLiteral(expr as NumericLiteral); + return +(expr as NumericLiteral).text; + case SyntaxKind.ParenthesizedExpression: + return evaluate((expr as ParenthesizedExpression).expression, location); + case SyntaxKind.Identifier: + if (isInfinityOrNaNString((expr as Identifier).escapedText)) { + return +((expr as Identifier).escapedText); + } + // falls through + case SyntaxKind.PropertyAccessExpression: + if (isEntityNameExpression(expr)) { + const symbol = resolveEntityName(expr, SymbolFlags.Value, /*ignoreErrors*/ true); + if (symbol) { + if (symbol.flags & SymbolFlags.EnumMember) { + return evaluateEnumMember(expr, symbol, location); + } + if (isConstVariable(symbol)) { + const declaration = symbol.valueDeclaration as VariableDeclaration | undefined; + if (declaration && !declaration.type && declaration.initializer && declaration !== location && isBlockScopedNameDeclaredBeforeUse(declaration, location)) { + return evaluate(declaration.initializer, declaration); + } + } + } + } + break; + case SyntaxKind.ElementAccessExpression: + const root = (expr as ElementAccessExpression).expression; + if (isEntityNameExpression(root) && isStringLiteralLike((expr as ElementAccessExpression).argumentExpression)) { + const rootSymbol = resolveEntityName(root, SymbolFlags.Value, /*ignoreErrors*/ true); + if (rootSymbol && rootSymbol.flags & SymbolFlags.Enum) { + const name = escapeLeadingUnderscores(((expr as ElementAccessExpression).argumentExpression as StringLiteralLike).text); + const member = rootSymbol.exports!.get(name); + if (member) { + return evaluateEnumMember(expr, member, location); + } + } + } + break; } + return undefined; } - /** - * 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 || getSourceFileOfNode(name).impliedNodeFormat === ModuleKind.CommonJS)) { - error(name, Diagnostics.Class_name_cannot_be_Object_when_targeting_ES5_with_module_0, ModuleKind[moduleKind]); // https://github.com/Microsoft/TypeScript/issues/17494 + function evaluateEnumMember(expr: Expression, symbol: Symbol, location: Declaration) { + const declaration = symbol.valueDeclaration; + if (!declaration || declaration === location) { + error(expr, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(symbol)); + return undefined; + } + if (!isBlockScopedNameDeclaredBeforeUse(declaration, location)) { + error(expr, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums); + return 0; + } + return getEnumMemberValue(declaration as EnumMember); + } + + function evaluateTemplateExpression(expr: TemplateExpression, location: Declaration) { + let result = expr.head.text; + for (const span of expr.templateSpans) { + const value = evaluate(span.expression, location); + if (value === undefined) { + return undefined; + } + result += value; + result += span.literal.text; } + return result; } - function checkUnmatchedJSDocParameters(node: SignatureDeclaration) { - const jsdocParameters = filter(getJSDocTags(node), isJSDocParameterTag); - if (!length(jsdocParameters)) return; + function checkEnumDeclaration(node: EnumDeclaration) { + addLazyDiagnostic(() => checkEnumDeclarationWorker(node)); + } - const isJs = isInJSFile(node); - const parameters = new Set<__String>(); - const excludedParameters = new Set(); - forEach(node.parameters, ({ name }, index) => { - if (isIdentifier(name)) { - parameters.add(name.escapedText); - } - if (isBindingPattern(name)) { - excludedParameters.add(index); - } - }); + function checkEnumDeclarationWorker(node: EnumDeclaration) { + // Grammar checking + checkGrammarModifiers(node); - const containsArguments = containsArgumentsReference(node); - if (containsArguments) { - const lastJSDocParamIndex = jsdocParameters.length - 1; - const lastJSDocParam = jsdocParameters[lastJSDocParamIndex]; - if (isJs && lastJSDocParam && isIdentifier(lastJSDocParam.name) && lastJSDocParam.typeExpression && - lastJSDocParam.typeExpression.type && !parameters.has(lastJSDocParam.name.escapedText) && !excludedParameters.has(lastJSDocParamIndex) && !isArrayType(getTypeFromTypeNode(lastJSDocParam.typeExpression.type))) { - error(lastJSDocParam.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(lastJSDocParam.name)); + checkCollisionsForDeclarationName(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 = getSymbolOfDeclaration(node); + const firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind); + if (node === firstDeclaration) { + if (enumSymbol.declarations && 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); + } + }); } - } - else { - forEach(jsdocParameters, ({ name, isNameFirst }, index) => { - if (excludedParameters.has(index) || isIdentifier(name) && parameters.has(name.escapedText)) { - return; + + 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; } - if (isQualifiedName(name)) { - if (isJs) { - error(name, Diagnostics.Qualified_name_0_is_not_allowed_without_a_leading_param_object_1, entityNameToString(name), entityNameToString(name.left)); - } + + const enumDeclaration = declaration as EnumDeclaration; + if (!enumDeclaration.members.length) { + return false; } - else { - if (!isNameFirst) { - errorOrSuggestion(isJs, name, Diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name, idText(name)); + + 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; } } }); } } - /** - * Check each type parameter and check that type parameters have no duplicate type parameter declarations - */ - function checkTypeParameters(typeParameterDeclarations: readonly TypeParameterDeclaration[] | undefined) { - let seenDefault = false; - if (typeParameterDeclarations) { - for (let i = 0; i < typeParameterDeclarations.length; i++) { - const node = typeParameterDeclarations[i]; - checkTypeParameter(node); - - addLazyDiagnostic(createCheckTypeParameterDiagnostic(node, i)); - } + function checkEnumMember(node: EnumMember) { + if (isPrivateIdentifier(node.name)) { + error(node, Diagnostics.An_enum_member_cannot_be_named_with_a_private_identifier); } - - function createCheckTypeParameterDiagnostic(node: TypeParameterDeclaration, i: number) { - return () => { - 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)); - } - } - }; + if (node.initializer) { + checkExpression(node.initializer); } } - /** 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 as TypeReferenceNode); - if (type.flags & TypeFlags.TypeParameter) { - for (let i = index; i < typeParameters.length; i++) { - if (type.symbol === getSymbolOfDeclaration(typeParameters[i])) { - error(node, Diagnostics.Type_parameter_defaults_can_only_reference_previously_declared_type_parameters); - } - } + function getFirstNonAmbientClassOrFunctionDeclaration(symbol: Symbol): Declaration | undefined { + const declarations = symbol.declarations; + if (declarations) { + for (const declaration of declarations) { + if ((declaration.kind === SyntaxKind.ClassDeclaration || + (declaration.kind === SyntaxKind.FunctionDeclaration && nodeIsPresent((declaration as FunctionLikeDeclaration).body))) && + !(declaration.flags & NodeFlags.Ambient)) { + return declaration; } } - forEachChild(node, visit); } + return undefined; } - /** Check that type parameter lists are identical across multiple declarations */ - function checkTypeParameterListsIdentical(symbol: Symbol) { - if (symbol.declarations && symbol.declarations.length === 1) { - return; + 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; } + } - const links = getSymbolLinks(symbol); - if (!links.typeParametersChecked) { - links.typeParametersChecked = true; - const declarations = getClassOrInterfaceDeclarationsOfSymbol(symbol); - if (!declarations || declarations.length <= 1) { + function checkModuleDeclaration(node: ModuleDeclaration) { + if (node.body) { + checkSourceElement(node.body); + if (!isGlobalScopeAugmentation(node)) { + registerForUnusedIdentifiersCheck(node); + } + } + + addLazyDiagnostic(checkModuleDeclarationDiagnostics); + + function checkModuleDeclarationDiagnostics() { + // 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: boolean = 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_at_the_top_level_of_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; } - const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; - if (!areTypeParametersIdentical(declarations, type.localTypeParameters!, getEffectiveTypeParameterDeclarations)) { - // 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); + if (!checkGrammarModifiers(node)) { + if (!inAmbientContext && node.name.kind === SyntaxKind.StringLiteral) { + grammarErrorOnNode(node.name, Diagnostics.Only_ambient_modules_can_use_quoted_names); } } - } - } - - function areTypeParametersIdentical(declarations: readonly T[], targetParameters: TypeParameter[], getTypeParameterDeclarations: (node: T) => readonly TypeParameterDeclaration[]) { - 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 = getTypeParameterDeclarations(declaration); - const numTypeParameters = sourceParameters.length; - if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) { - return false; + if (isIdentifier(node.name)) { + checkCollisionsForDeclarationName(node, node.name); } - for (let i = 0; i < numTypeParameters; i++) { - const source = sourceParameters[i]; - const target = targetParameters[i]; + checkExportsOnMergedDeclarations(node); + const symbol = getSymbolOfDeclaration(node); - // 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; + // The following checks only apply on a non-ambient instantiated module declaration. + if (symbol.flags & SymbolFlags.ValueModule + && !inAmbientContext + && isInstantiatedModule(node, shouldPreserveConstEnums(compilerOptions)) + ) { + if (getIsolatedModules(compilerOptions) && !getSourceFileOfNode(node).externalModuleIndicator) { + // This could be loosened a little if needed. The only problem we are trying to avoid is unqualified + // references to namespace members declared in other files. But use of namespaces is discouraged anyway, + // so for now we will just not allow them in scripts, which is the only place they can merge cross-file. + error(node.name, Diagnostics.Namespaces_are_not_allowed_in_global_script_files_when_0_is_enabled_If_this_file_is_not_intended_to_be_a_global_script_set_moduleDetection_to_force_or_add_an_empty_export_statement, isolatedModulesLikeFlagName); } + if (symbol.declarations?.length! > 1) { + 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 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 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 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; + if (compilerOptions.verbatimModuleSyntax && + node.parent.kind === SyntaxKind.SourceFile && + (moduleKind === ModuleKind.CommonJS || node.parent.impliedNodeFormat === ModuleKind.CommonJS) + ) { + const exportModifier = node.modifiers?.find(m => m.kind === SyntaxKind.ExportKeyword); + if (exportModifier) { + error(exportModifier, Diagnostics.A_top_level_export_modifier_cannot_be_used_on_value_declarations_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); + } } } - } - - return true; - } - function getFirstTransformableStaticClassElement(node: ClassLikeDeclaration) { - const willTransformStaticElementsOfDecoratedClass = - !legacyDecorators && languageVersion < ScriptTarget.ESNext && - classOrConstructorParameterIsDecorated(/*useLegacyDecorators*/ false, node); - const willTransformPrivateElementsOrClassStaticBlocks = languageVersion <= ScriptTarget.ES2022; - const willTransformInitializers = !useDefineForClassFields || languageVersion < ScriptTarget.ES2022; - if (willTransformStaticElementsOfDecoratedClass || willTransformPrivateElementsOrClassStaticBlocks) { - for (const member of node.members) { - if (willTransformStaticElementsOfDecoratedClass && classElementOrClassElementParameterIsDecorated(/*useLegacyDecorators*/ false, member, node)) { - return firstOrUndefined(getDecorators(node)) ?? node; + 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 || (getSymbolOfDeclaration(node).flags & SymbolFlags.Transient); + if (checkBody && node.body) { + for (const statement of node.body.statements) { + checkModuleAugmentationElement(statement, isGlobalAugmentation); + } + } } - else if (willTransformPrivateElementsOrClassStaticBlocks) { - if (isClassStaticBlockDeclaration(member)) { - return member; + 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 (isStatic(member)) { - if (isPrivateIdentifierClassElementDeclaration(member) || - willTransformInitializers && isInitializedProperty(member)) { - return member; - } + 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); } } } } } - function checkClassExpressionExternalHelpers(node: ClassExpression) { - if (node.name) return; - - const parent = walkUpOuterExpressions(node); - if (!isNamedEvaluationSource(parent)) return; - - const willTransformESDecorators = !legacyDecorators && languageVersion < ScriptTarget.ESNext; - let location: Node | undefined; - if (willTransformESDecorators && classOrConstructorParameterIsDecorated(/*useLegacyDecorators*/ false, node)) { - location = firstOrUndefined(getDecorators(node)) ?? node; - } - else { - location = getFirstTransformableStaticClassElement(node); - } - - if (location) { - checkExternalEmitHelpers(location, ExternalEmitHelpers.SetFunctionName); - if ((isPropertyAssignment(parent) || isPropertyDeclaration(parent) || isBindingElement(parent)) && isComputedPropertyName(parent.name)) { - checkExternalEmitHelpers(location, ExternalEmitHelpers.PropKey); - } + 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 as VariableStatement).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 as VariableDeclaration | BindingElement).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; + } + break; } } - function checkClassExpression(node: ClassExpression): Type { - checkClassLikeDeclaration(node); - checkNodeDeferred(node); - checkClassExpressionExternalHelpers(node); - return getTypeOfSymbol(getSymbolOfDeclaration(node)); - } - - function checkClassExpressionDeferred(node: ClassExpression) { - forEach(node.members, checkSourceElement); - registerForUnusedIdentifiersCheck(node); + 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 checkClassDeclaration(node: ClassDeclaration) { - const firstDecorator = find(node.modifiers, isDecorator); - if (legacyDecorators && firstDecorator && some(node.members, p => hasStaticModifier(p) && isPrivateIdentifierClassElementDeclaration(p))) { - grammarErrorOnNode(firstDecorator, Diagnostics.Class_decorators_can_t_be_used_with_static_private_identifier_Consider_removing_the_experimental_decorator); + function checkExternalImportOrExportDeclaration(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): boolean { + const moduleName = getExternalModuleName(node); + if (!moduleName || nodeIsMissing(moduleName)) { + // Should be a parse error. + return false; } - if (!node.name && !hasSyntacticModifier(node, ModifierFlags.Default)) { - grammarErrorOnFirstToken(node, Diagnostics.A_class_declaration_without_the_default_modifier_must_have_a_name); + if (!isStringLiteral(moduleName)) { + error(moduleName, Diagnostics.String_literal_expected); + return false; } - checkClassLikeDeclaration(node); - forEach(node.members, checkSourceElement); - - registerForUnusedIdentifiersCheck(node); + 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; + } + } + if (!isImportEqualsDeclaration(node) && node.assertClause) { + let hasError = false; + for (const clause of node.assertClause.elements) { + if (!isStringLiteral(clause.value)) { + hasError = true; + error(clause.value, Diagnostics.Import_assertion_values_must_be_string_literal_expressions); + } + } + return !hasError; + } + return true; } - function checkClassLikeDeclaration(node: ClassLikeDeclaration) { - checkGrammarClassLikeDeclaration(node); - checkDecorators(node); - checkCollisionsForDeclarationName(node, node.name); - checkTypeParameters(getEffectiveTypeParameterDeclarations(node)); - checkExportsOnMergedDeclarations(node); - const symbol = getSymbolOfDeclaration(node); - const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; - const typeWithThis = getTypeWithThisArgument(type); - const staticType = getTypeOfSymbol(symbol) as ObjectType; - checkTypeParameterListsIdentical(symbol); - checkFunctionOrConstructorSymbol(symbol); - checkClassForDuplicateDeclarations(node); + function checkAliasSymbol(node: AliasDeclarationNode) { + let symbol = getSymbolOfDeclaration(node); + const target = resolveAlias(symbol); - // Only check for reserved static identifiers on non-ambient context. - const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient); - if (!nodeInAmbientContext) { - checkClassForStaticPropertyNameConflicts(node); - } + 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 baseTypeNode = getEffectiveBaseTypeNode(node); - if (baseTypeNode) { - forEach(baseTypeNode.typeArguments, checkSourceElement); - if (languageVersion < ScriptTarget.ES2015) { - checkExternalEmitHelpers(baseTypeNode.parent, ExternalEmitHelpers.Extends); + // A type-only import/export will already have a grammar error in a JS file, so no need to issue more errors within + if (isInJSFile(node) && !(target.flags & SymbolFlags.Value) && !isTypeOnlyImportOrExportDeclaration(node)) { + const errorNode = + isImportOrExportSpecifier(node) ? node.propertyName || node.name : + isNamedDeclaration(node) ? node.name : + node; + + Debug.assert(node.kind !== SyntaxKind.NamespaceExport); + if (node.kind === SyntaxKind.ExportSpecifier) { + const diag = error(errorNode, Diagnostics.Types_cannot_appear_in_export_declarations_in_JavaScript_files); + const alreadyExportedSymbol = getSourceFileOfNode(node).symbol?.exports?.get((node.propertyName || node.name).escapedText); + if (alreadyExportedSymbol === target) { + const exportingDeclaration = alreadyExportedSymbol.declarations?.find(isJSDocNode); + if (exportingDeclaration) { + addRelatedInfo(diag, createDiagnosticForNode( + exportingDeclaration, + Diagnostics._0_is_automatically_exported_here, + unescapeLeadingUnderscores(alreadyExportedSymbol.escapedName))); + } + } + } + else { + Debug.assert(node.kind !== SyntaxKind.VariableDeclaration); + const importDeclaration = findAncestor(node, or(isImportDeclaration, isImportEqualsDeclaration)); + const moduleSpecifier = (importDeclaration && tryGetModuleSpecifierFromDeclaration(importDeclaration)?.text) ?? "..."; + const importedIdentifier = unescapeLeadingUnderscores(isIdentifier(errorNode) ? errorNode.escapedText : symbol.escapedName); + error( + errorNode, + Diagnostics._0_is_a_type_and_cannot_be_imported_in_JavaScript_files_Use_1_in_a_JSDoc_type_annotation, + importedIdentifier, + `import("${moduleSpecifier}").${importedIdentifier}`); + } + return; } - // check both @extends and extends if both are specified. - const extendsNode = getClassExtendsHeritageElement(node); - if (extendsNode && extendsNode !== baseTypeNode) { - checkExpression(extendsNode.expression); + + const targetFlags = getAllSymbolFlags(target); + 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 (targetFlags & 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)); } - const baseTypes = getBaseTypes(type); - if (baseTypes.length) { - addLazyDiagnostic(() => { - 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; + if (getIsolatedModules(compilerOptions) + && !isTypeOnlyImportOrExportDeclaration(node) + && !(node.flags & NodeFlags.Ambient)) { + const typeOnlyAlias = getTypeOnlyAliasDeclaration(symbol); + const isType = !(targetFlags & SymbolFlags.Value); + if (isType || typeOnlyAlias) { + switch (node.kind) { + case SyntaxKind.ImportClause: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ImportEqualsDeclaration: { + if (compilerOptions.preserveValueImports || compilerOptions.verbatimModuleSyntax) { + Debug.assertIsDefined(node.name, "An ImportClause with a symbol should have a name"); + const message = compilerOptions.verbatimModuleSyntax && isInternalModuleImportEqualsDeclaration(node) + ? Diagnostics.An_import_alias_cannot_resolve_to_a_type_or_type_only_declaration_when_verbatimModuleSyntax_is_enabled + : isType + ? compilerOptions.verbatimModuleSyntax + ? Diagnostics._0_is_a_type_and_must_be_imported_using_a_type_only_import_when_verbatimModuleSyntax_is_enabled + : Diagnostics._0_is_a_type_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled + : compilerOptions.verbatimModuleSyntax + ? Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_verbatimModuleSyntax_is_enabled + : Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled; + const name = idText(node.kind === SyntaxKind.ImportSpecifier ? node.propertyName || node.name : node.name); + addTypeOnlyDeclarationRelatedInfo( + error(node, message, name), + isType ? undefined : typeOnlyAlias, + name + ); } + if (isType && node.kind === SyntaxKind.ImportEqualsDeclaration && hasEffectiveModifier(node, ModifierFlags.Export)) { + error(node, Diagnostics.Cannot_use_export_import_on_a_type_or_type_only_namespace_when_0_is_enabled, isolatedModulesLikeFlagName); + } + 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) { - if (!isMixinConstructorType(staticType)) { - error(node.name || node, Diagnostics.A_mixin_class_must_have_a_constructor_with_a_single_rest_parameter_of_type_any); - } - else { - const constructSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); - if (constructSignatures.some(signature => signature.flags & SignatureFlags.Abstract) && !hasSyntacticModifier(node, ModifierFlags.Abstract)) { - error(node.name || node, Diagnostics.A_mixin_class_that_extends_from_a_type_variable_containing_an_abstract_construct_signature_must_also_be_declared_abstract); + case SyntaxKind.ExportSpecifier: { + // Don't allow re-exporting an export that will be elided when `--isolatedModules` is set. + // The exception is that `import type { A } from './a'; export { A }` is allowed + // because single-file analysis can determine that the export should be dropped. + if (compilerOptions.verbatimModuleSyntax || getSourceFileOfNode(typeOnlyAlias) !== getSourceFileOfNode(node)) { + const name = idText(node.propertyName || node.name); + const diagnostic = isType + ? error(node, Diagnostics.Re_exporting_a_type_when_0_is_enabled_requires_using_export_type, isolatedModulesLikeFlagName) + : error(node, Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_re_exported_using_a_type_only_re_export_when_1_is_enabled, name, isolatedModulesLikeFlagName); + addTypeOnlyDeclarationRelatedInfo(diagnostic, isType ? undefined : typeOnlyAlias, name); + break; } } } + } - 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); - }); + if (compilerOptions.verbatimModuleSyntax && + node.kind !== SyntaxKind.ImportEqualsDeclaration && + !isInJSFile(node) && + (moduleKind === ModuleKind.CommonJS || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) + ) { + error(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); + } } - } - - checkMembersForOverrideModifier(node, type, typeWithThis, staticType); - const implementedTypeNodes = getEffectiveImplementsTypeNodes(node); - if (implementedTypeNodes) { - for (const typeRefNode of implementedTypeNodes) { - if (!isEntityNameExpression(typeRefNode.expression) || isOptionalChain(typeRefNode.expression)) { - error(typeRefNode.expression, Diagnostics.A_class_can_only_implement_an_identifier_Slashqualified_name_with_optional_type_arguments); + if (isImportSpecifier(node)) { + const targetSymbol = checkDeprecatedAliasedSymbol(symbol, node); + if (isDeprecatedAliasedSymbol(targetSymbol) && targetSymbol.declarations) { + addDeprecatedSuggestion(node, targetSymbol.declarations, targetSymbol.escapedName as string); } - checkTypeReferenceNode(typeRefNode); - addLazyDiagnostic(createImplementsDiagnostics(typeRefNode)); } } + } - addLazyDiagnostic(() => { - checkIndexConstraints(type, symbol); - checkIndexConstraints(staticType, symbol, /*isStaticIndex*/ true); - checkTypeForDuplicateIndexSignatures(node); - checkPropertyInitialization(node); - }); + function isDeprecatedAliasedSymbol(symbol: Symbol) { + return !!symbol.declarations && every(symbol.declarations, d => !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated)); + } - function createImplementsDiagnostics(typeRefNode: ExpressionWithTypeArguments) { - return () => { - const t = getReducedType(getTypeFromTypeNode(typeRefNode)); - if (!isErrorType(t)) { - 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); - } + function checkDeprecatedAliasedSymbol(symbol: Symbol, location: Node) { + if (!(symbol.flags & SymbolFlags.Alias)) return symbol; + + const targetSymbol = resolveAlias(symbol); + if (targetSymbol === unknownSymbol) return targetSymbol; + + while (symbol.flags & SymbolFlags.Alias) { + const target = getImmediateAliasedSymbol(symbol); + if (target) { + if (target === targetSymbol) break; + if (target.declarations && length(target.declarations)) { + if (isDeprecatedAliasedSymbol(target)) { + addDeprecatedSuggestion(location, target.declarations, target.escapedName as string); + break; } else { - error(typeRefNode, Diagnostics.A_class_can_only_implement_an_object_type_or_intersection_of_object_types_with_statically_known_members); + if (symbol === targetSymbol) break; + symbol = target; } } - }; - } - } - - function checkMembersForOverrideModifier(node: ClassLikeDeclaration, type: InterfaceType, typeWithThis: Type, staticType: ObjectType) { - const baseTypeNode = getEffectiveBaseTypeNode(node); - const baseTypes = baseTypeNode && getBaseTypes(type); - const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined; - const baseStaticType = getBaseConstructorTypeOfClass(type); - - for (const member of node.members) { - if (hasAmbientModifier(member)) { - continue; } - - if (isConstructorDeclaration(member)) { - forEach(member.parameters, param => { - if (isParameterPropertyDeclaration(param, member)) { - checkExistingMemberForOverrideModifier( - node, - staticType, - baseStaticType, - baseWithThis, - type, - typeWithThis, - param, - /*memberIsParameterProperty*/ true - ); - } - }); + else { + break; } - checkExistingMemberForOverrideModifier( - node, - staticType, - baseStaticType, - baseWithThis, - type, - typeWithThis, - member, - /*memberIsParameterProperty*/ false, - ); } + return targetSymbol; } - /** - * @param member Existing member node to be checked. - * Note: `member` cannot be a synthetic node. - */ - function checkExistingMemberForOverrideModifier( - node: ClassLikeDeclaration, - staticType: ObjectType, - baseStaticType: Type, - baseWithThis: Type | undefined, - type: InterfaceType, - typeWithThis: Type, - member: ClassElement | ParameterPropertyDeclaration, - memberIsParameterProperty: boolean, - reportErrors = true, - ): MemberOverrideStatus { - const declaredProp = member.name - && getSymbolAtLocation(member.name) - || getSymbolAtLocation(member); - if (!declaredProp) { - return MemberOverrideStatus.Ok; + function checkImportBinding(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier) { + checkCollisionsForDeclarationName(node, node.name); + checkAliasSymbol(node); + if (node.kind === SyntaxKind.ImportSpecifier && + idText(node.propertyName || node.name) === "default" && + getESModuleInterop(compilerOptions) && + moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); } - - return checkMemberForOverrideModifier( - node, - staticType, - baseStaticType, - baseWithThis, - type, - typeWithThis, - hasOverrideModifier(member), - hasAbstractModifier(member), - isStatic(member), - memberIsParameterProperty, - symbolName(declaredProp), - reportErrors ? member : undefined, - ); } - /** - * Checks a class member declaration for either a missing or an invalid `override` modifier. - * Note: this function can be used for speculative checking, - * i.e. checking a member that does not yet exist in the program. - * An example of that would be to call this function in a completions scenario, - * when offering a method declaration as completion. - * @param errorNode The node where we should report an error, or undefined if we should not report errors. - */ - function checkMemberForOverrideModifier( - node: ClassLikeDeclaration, - staticType: ObjectType, - baseStaticType: Type, - baseWithThis: Type | undefined, - type: InterfaceType, - typeWithThis: Type, - memberHasOverrideModifier: boolean, - memberHasAbstractModifier: boolean, - memberIsStatic: boolean, - memberIsParameterProperty: boolean, - memberName: string, - errorNode?: Node, - ): MemberOverrideStatus { - const isJs = isInJSFile(node); - const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient); - if (baseWithThis && (memberHasOverrideModifier || compilerOptions.noImplicitOverride)) { - const memberEscapedName = escapeLeadingUnderscores(memberName); - const thisType = memberIsStatic ? staticType : typeWithThis; - const baseType = memberIsStatic ? baseStaticType : baseWithThis; - const prop = getPropertyOfType(thisType, memberEscapedName); - const baseProp = getPropertyOfType(baseType, memberEscapedName); - - const baseClassName = typeToString(baseWithThis); - if (prop && !baseProp && memberHasOverrideModifier) { - if (errorNode) { - const suggestion = getSuggestedSymbolForNonexistentClassMember(memberName, baseType); // Again, using symbol name: note that's different from `symbol.escapedName` - suggestion ? - error( - errorNode, - isJs ? - Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1 : - Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0_Did_you_mean_1, - baseClassName, - symbolToString(suggestion)) : - error( - errorNode, - isJs ? - Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_it_is_not_declared_in_the_base_class_0 : - Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0, - baseClassName); - } - return MemberOverrideStatus.HasInvalidOverride; - } - else if (prop && baseProp?.declarations && compilerOptions.noImplicitOverride && !nodeInAmbientContext) { - const baseHasAbstract = some(baseProp.declarations, hasAbstractModifier); - if (memberHasOverrideModifier) { - return MemberOverrideStatus.Ok; + function checkAssertClause(declaration: ImportDeclaration | ExportDeclaration) { + if (declaration.assertClause) { + const validForTypeAssertions = isExclusivelyTypeOnlyImportOrExport(declaration); + const override = getResolutionModeOverrideForClause(declaration.assertClause, validForTypeAssertions ? grammarErrorOnNode : undefined); + if (validForTypeAssertions && override) { + if (!isNightly()) { + grammarErrorOnNode(declaration.assertClause, Diagnostics.resolution_mode_assertions_are_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next); } - if (!baseHasAbstract) { - if (errorNode) { - const diag = memberIsParameterProperty ? - isJs ? - Diagnostics.This_parameter_property_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0 : - Diagnostics.This_parameter_property_must_have_an_override_modifier_because_it_overrides_a_member_in_base_class_0 : - isJs ? - Diagnostics.This_member_must_have_a_JSDoc_comment_with_an_override_tag_because_it_overrides_a_member_in_the_base_class_0 : - Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0; - error(errorNode, diag, baseClassName); - } - return MemberOverrideStatus.NeedsOverride; - } - else if (memberHasAbstractModifier && baseHasAbstract) { - if (errorNode) { - error(errorNode, Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0, baseClassName); - } - return MemberOverrideStatus.NeedsOverride; + if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node16 && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) { + return grammarErrorOnNode(declaration.assertClause, Diagnostics.resolution_mode_assertions_are_only_supported_when_moduleResolution_is_node16_or_nodenext); } + return; // Other grammar checks do not apply to type-only imports with resolution mode assertions } - } - else if (memberHasOverrideModifier) { - if (errorNode) { - const className = typeToString(type); - error( - errorNode, - isJs ? - Diagnostics.This_member_cannot_have_a_JSDoc_comment_with_an_override_tag_because_its_containing_class_0_does_not_extend_another_class : - Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class, - className); - } - return MemberOverrideStatus.HasInvalidOverride; - } - return MemberOverrideStatus.Ok; - } - - 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 (isStatic(member)) { - continue; + const mode = (moduleKind === ModuleKind.NodeNext) && declaration.moduleSpecifier && getUsageModeForExpression(declaration.moduleSpecifier); + if (mode !== ModuleKind.ESNext && moduleKind !== ModuleKind.ESNext) { + return grammarErrorOnNode(declaration.assertClause, + moduleKind === ModuleKind.NodeNext + ? Diagnostics.Import_assertions_are_not_allowed_on_statements_that_transpile_to_commonjs_require_calls + : Diagnostics.Import_assertions_are_only_supported_when_the_module_option_is_set_to_esnext_or_nodenext); } - 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, /*headMessage*/ undefined, rootChain)) { - issuedMemberError = true; - } - } + + if (isImportDeclaration(declaration) ? declaration.importClause?.isTypeOnly : declaration.isTypeOnly) { + return grammarErrorOnNode(declaration.assertClause, Diagnostics.Import_assertions_cannot_be_used_with_type_only_imports_or_exports); } - } - 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)); - } + if (override) { + return grammarErrorOnNode(declaration.assertClause, Diagnostics.resolution_mode_can_only_be_set_for_type_only_imports); } } } - /** - * Checks a member declaration node to see if has a missing or invalid `override` modifier. - * @param node Class-like node where the member is declared. - * @param member Member declaration node. - * @param memberSymbol Member symbol. - * Note: `member` can be a synthetic node without a parent. - */ - function getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement, memberSymbol: Symbol): MemberOverrideStatus { - if (!member.name) { - return MemberOverrideStatus.Ok; + function checkImportDeclaration(node: ImportDeclaration) { + if (checkGrammarModuleElementContext(node, isInJSFile(node) ? Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_module : Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_namespace_or_module)) { + // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. + return; } - - const classSymbol = getSymbolOfDeclaration(node); - const type = getDeclaredTypeOfSymbol(classSymbol) as InterfaceType; - const typeWithThis = getTypeWithThisArgument(type); - const staticType = getTypeOfSymbol(classSymbol) as ObjectType; - - const baseTypeNode = getEffectiveBaseTypeNode(node); - const baseTypes = baseTypeNode && getBaseTypes(type); - const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined; - const baseStaticType = getBaseConstructorTypeOfClass(type); - - const memberHasOverrideModifier = member.parent - ? hasOverrideModifier(member) - : hasSyntacticModifier(member, ModifierFlags.Override); - - return checkMemberForOverrideModifier( - node, - staticType, - baseStaticType, - baseWithThis, - type, - typeWithThis, - memberHasOverrideModifier, - hasAbstractModifier(member), - isStatic(member), - /*memberIsParameterProperty*/ false, - symbolName(memberSymbol), - ); - } - - 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 - // NOTE: cast to TransientSymbol should be safe because only TransientSymbols have CheckFlags.Instantiated - return getCheckFlags(s) & CheckFlags.Instantiated ? (s as TransientSymbol).links.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); - let inheritedAbstractMemberNotImplementedError: Diagnostic | undefined; - 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 (!checkGrammarModifiers(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 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && getESModuleInterop(compilerOptions)) { + // import * as ns from "foo"; + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); } } - - if (!inheritedAbstractMemberNotImplementedError) { - inheritedAbstractMemberNotImplementedError = error( - derivedClassDecl, - Diagnostics.Non_abstract_class_0_does_not_implement_all_abstract_members_of_1, - typeToString(type), typeToString(baseType)); - - } - if (derivedClassDecl.kind === SyntaxKind.ClassExpression) { - addRelatedInfo( - inheritedAbstractMemberNotImplementedError, - createDiagnosticForNode( - baseProperty.valueDeclaration ?? (baseProperty.declarations && first(baseProperty.declarations)) ?? derivedClassDecl, - Diagnostics.Non_abstract_class_expression_does_not_implement_inherited_abstract_member_0_from_class_1, - symbolToString(baseProperty), typeToString(baseType))); - } else { - addRelatedInfo( - inheritedAbstractMemberNotImplementedError, - createDiagnosticForNode( - baseProperty.valueDeclaration ?? (baseProperty.declarations && first(baseProperty.declarations)) ?? derivedClassDecl, - Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2, - typeToString(type), symbolToString(baseProperty), typeToString(baseType))); + const moduleExisted = resolveExternalModuleName(node, node.moduleSpecifier); + if (moduleExisted) { + forEach(importClause.namedBindings.elements, checkImportBinding); + } } } } - 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; - } + } + checkAssertClause(node); + if (node.isTsPlusGlobal) { + const location = (node.moduleSpecifier as StringLiteral).text; + if (pathIsRelative(location)) { + error(node.moduleSpecifier, Diagnostics.Global_import_path_cannot_be_relative); + } + } + } - 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 ((getCheckFlags(base) & CheckFlags.Synthetic - ? base.declarations?.some(d => isPropertyAbstractOrInterface(d, baseDeclarationFlags)) - : base.declarations?.every(d => isPropertyAbstractOrInterface(d, baseDeclarationFlags))) - || getCheckFlags(base) & CheckFlags.Mapped - || derived.valueDeclaration && isBinaryExpression(derived.valueDeclaration)) { - // when the base property is abstract or from an interface, base/derived flags don't need to match - // for intersection properties, this must be true of *any* of the declarations, for others it must be true of *all* - // same when the derived property is from an assignment - continue; - } + function checkImportEqualsDeclaration(node: ImportEqualsDeclaration) { + if (checkGrammarModuleElementContext(node, isInJSFile(node) ? Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_module : Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_namespace_or_module)) { + // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. + return; + } - 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 (useDefineForClassFields) { - const uninitialized = derived.declarations?.find(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)); - } + checkGrammarModifiers(node); + if (isInternalModuleImportEqualsDeclaration(node) || checkExternalImportOrExportDeclaration(node)) { + checkImportBinding(node); + if (hasSyntacticModifier(node, ModifierFlags.Export)) { + markExportAsReferenced(node); + } + if (node.moduleReference.kind !== SyntaxKind.ExternalModuleReference) { + const target = resolveAlias(getSymbolOfDeclaration(node)); + if (target !== unknownSymbol) { + const targetFlags = getAllSymbolFlags(target); + if (targetFlags & 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)); } } - - // 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; + if (targetFlags & SymbolFlags.Type) { + checkTypeNameIsReserved(node.name, Diagnostics.Import_name_cannot_be_0); } } - 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; + if (node.isTypeOnly) { + grammarErrorOnNode(node, Diagnostics.An_import_alias_cannot_use_import_type); } - else { - errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function; + } + else { + if (moduleKind >= ModuleKind.ES2015 && getSourceFileOfNode(node).impliedNodeFormat === undefined && !node.isTypeOnly && !(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); } - - error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, typeToString(baseType), symbolToString(base), typeToString(type)); } } } - function isPropertyAbstractOrInterface(declaration: Declaration, baseDeclarationFlags: ModifierFlags) { - return baseDeclarationFlags & ModifierFlags.Abstract && (!isPropertyDeclaration(declaration) || !declaration.initializer) - || isInterfaceDeclaration(declaration.parent); - } + function checkExportDeclaration(node: ExportDeclaration) { + if (checkGrammarModuleElementContext(node, isInJSFile(node) ? Diagnostics.An_export_declaration_can_only_be_used_at_the_top_level_of_a_module : Diagnostics.An_export_declaration_can_only_be_used_at_the_top_level_of_a_namespace_or_module)) { + // If we hit an export in an illegal context, just bail out to avoid cascading errors. + return; + } - function getNonInheritedProperties(type: InterfaceType, baseTypes: BaseType[], properties: Symbol[]) { - if (!length(baseTypes)) { - return properties; + if (!checkGrammarModifiers(node) && hasSyntacticModifiers(node)) { + grammarErrorOnFirstToken(node, Diagnostics.An_export_declaration_cannot_have_modifiers); } - 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 && prop.parent === existing.parent) { - seen.delete(prop.escapedName); + 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 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { + 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 (getESModuleInterop(compilerOptions)) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); + } + } + else { + // export * from "foo" + checkExternalEmitHelpers(node, ExternalEmitHelpers.ExportStar); + } } } } + checkAssertClause(node); + } - return arrayFrom(seen.values()); + function checkGrammarExportDeclaration(node: ExportDeclaration): boolean { + if (node.isTypeOnly && node.exportClause?.kind === SyntaxKind.NamedExports) { + return checkGrammarNamedImportsOrExports(node.exportClause); + } + return false; } - function checkInheritedPropertiesAreIdentical(type: InterfaceType, typeNode: Node): boolean { - const baseTypes = getBaseTypes(type); - if (baseTypes.length < 2) { - return true; + 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; + } - 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 }); + function importClauseContainsReferencedImport(importClause: ImportClause) { + return forEachImportClauseDeclaration(importClause, declaration => { + return !!getSymbolOfDeclaration(declaration).isReferenced; }); - 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); + function importClauseContainsConstEnumUsedAsValue(importClause: ImportClause) { + return forEachImportClauseDeclaration(importClause, declaration => { + return !!getSymbolLinks(getSymbolOfDeclaration(declaration)).constEnumReferenced; + }); + } - 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(getSourceFileOfNode(typeNode), typeNode, errorInfo)); - } - } - } - } + function canConvertImportDeclarationToTypeOnly(statement: Statement) { + return isImportDeclaration(statement) && + statement.importClause && + !statement.importClause.isTypeOnly && + importClauseContainsReferencedImport(statement.importClause) && + !isReferencedAliasDeclaration(statement.importClause, /*checkChildren*/ true) && + !importClauseContainsConstEnumUsedAsValue(statement.importClause); + } - return ok; + function canConvertImportEqualsDeclarationToTypeOnly(statement: Statement) { + return isImportEqualsDeclaration(statement) && + isExternalModuleReference(statement.moduleReference) && + !statement.isTypeOnly && + getSymbolOfDeclaration(statement).isReferenced && + !isReferencedAliasDeclaration(statement, /*checkChildren*/ false) && + !getSymbolLinks(getSymbolOfDeclaration(statement)).constEnumReferenced; } - function checkPropertyInitialization(node: ClassLikeDeclaration) { - if (!strictNullChecks || !strictPropertyInitialization || node.flags & NodeFlags.Ambient) { + function checkImportsForTypeOnlyConversion(sourceFile: SourceFile) { + if (!canCollectSymbolAliasAccessabilityData) { return; } - const constructor = findConstructorDeclaration(node); - for (const member of node.members) { - if (getEffectiveModifierFlags(member) & ModifierFlags.Ambient) { - continue; - } - if (!isStatic(member) && isPropertyWithoutInitializer(member)) { - const propName = (member as PropertyDeclaration).name; - if (isIdentifier(propName) || isPrivateIdentifier(propName) || isComputedPropertyName(propName)) { - const type = getTypeOfSymbol(getSymbolOfDeclaration(member)); - if (!(type.flags & TypeFlags.AnyOrUnknown || containsUndefinedType(type))) { - 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)); - } - } - } + for (const statement of sourceFile.statements) { + if (canConvertImportDeclarationToTypeOnly(statement) || canConvertImportEqualsDeclarationToTypeOnly(statement)) { + error( + statement, + Diagnostics.This_import_is_never_used_as_a_value_and_must_use_import_type_because_importsNotUsedAsValues_is_set_to_error); } } } - function isPropertyWithoutInitializer(node: Node) { - return node.kind === SyntaxKind.PropertyDeclaration && - !hasAbstractModifier(node) && - !(node as PropertyDeclaration).exclamationToken && - !(node as PropertyDeclaration).initializer; - } - - function isPropertyInitializedInStaticBlocks(propName: Identifier | PrivateIdentifier, propType: Type, staticBlocks: readonly ClassStaticBlockDeclaration[], startPos: number, endPos: number) { - for (const staticBlock of staticBlocks) { - // static block must be within the provided range as they are evaluated in document order (unlike constructors) - if (staticBlock.pos >= startPos && staticBlock.pos <= endPos) { - const reference = factory.createPropertyAccessExpression(factory.createThis(), propName); - setParent(reference.expression, reference); - setParent(reference, staticBlock); - reference.flowNode = staticBlock.returnFlowNode; - const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); - if (!containsUndefinedType(flowType)) { - return true; + 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 || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { + error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName)); + } + else { + if (!node.isTypeOnly && !node.parent.parent.isTypeOnly) { + markExportAsReferenced(node); + } + const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol); + if (!target || getAllSymbolFlags(target) & SymbolFlags.Value) { + checkExpressionCached(node.propertyName || node.name); } } } - return false; + else { + if (getESModuleInterop(compilerOptions) && + moduleKind !== ModuleKind.System && + (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && + idText(node.propertyName || node.name) === "default") { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); + } + } } - function isPropertyInitializedInConstructor(propName: Identifier | PrivateIdentifier | ComputedPropertyName, propType: Type, constructor: ConstructorDeclaration) { - const reference = isComputedPropertyName(propName) - ? factory.createElementAccessExpression(factory.createThis(), propName.expression) - : factory.createPropertyAccessExpression(factory.createThis(), propName); - setParent(reference.expression, reference); - setParent(reference, constructor); - reference.flowNode = constructor.returnFlowNode; - const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType)); - return !containsUndefinedType(flowType); - } + function checkExportAssignment(node: ExportAssignment) { + const illegalContextMessage = node.isExportEquals + ? Diagnostics.An_export_assignment_must_be_at_the_top_level_of_a_file_or_module_declaration + : Diagnostics.A_default_export_must_be_at_the_top_level_of_a_file_or_module_declaration; + if (checkGrammarModuleElementContext(node, illegalContextMessage)) { + // 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 as ModuleDeclaration; + 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); + } - function checkInterfaceDeclaration(node: InterfaceDeclaration) { + return; + } // Grammar checking - if (!checkGrammarModifiers(node)) checkGrammarInterfaceDeclaration(node); + if (!checkGrammarModifiers(node) && hasEffectiveModifiers(node)) { + grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers); + } - checkTypeParameters(node.typeParameters); - addLazyDiagnostic(() => { - checkTypeNameIsReserved(node.name, Diagnostics.Interface_name_cannot_be_0); + const typeAnnotationNode = getEffectiveTypeAnnotationNode(node); + if (typeAnnotationNode) { + checkTypeAssignableTo(checkExpressionCached(node.expression), getTypeFromTypeNode(typeAnnotationNode), node.expression); + } - checkExportsOnMergedDeclarations(node); - const symbol = getSymbolOfDeclaration(node); - checkTypeParameterListsIdentical(symbol); + const isIllegalExportDefaultInCJS = !node.isExportEquals && + !(node.flags & NodeFlags.Ambient) && + compilerOptions.verbatimModuleSyntax && + (moduleKind === ModuleKind.CommonJS || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS); - // Only check this symbol once - const firstInterfaceDecl = getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration); - if (node === firstInterfaceDecl) { - const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType; - 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); + if (node.expression.kind === SyntaxKind.Identifier) { + const id = node.expression as Identifier; + const sym = getExportSymbolOfValueSymbolIfExported(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 }`) + if (getAllSymbolFlags(sym) & SymbolFlags.Value) { + // However if it is a value, we need to check it's being used correctly + checkExpressionCached(id); + if (!isIllegalExportDefaultInCJS && !(node.flags & NodeFlags.Ambient) && compilerOptions.verbatimModuleSyntax && getTypeOnlyAliasDeclaration(sym, SymbolFlags.Value)) { + error(id, + node.isExportEquals + ? Diagnostics.An_export_declaration_must_reference_a_real_value_when_verbatimModuleSyntax_is_enabled_but_0_resolves_to_a_type_only_declaration + : Diagnostics.An_export_default_must_reference_a_real_value_when_verbatimModuleSyntax_is_enabled_but_0_resolves_to_a_type_only_declaration, + idText(id)); } - checkIndexConstraints(type, symbol); + } + else if (!isIllegalExportDefaultInCJS && !(node.flags & NodeFlags.Ambient) && compilerOptions.verbatimModuleSyntax) { + error(id, + node.isExportEquals + ? Diagnostics.An_export_declaration_must_reference_a_value_when_verbatimModuleSyntax_is_enabled_but_0_only_refers_to_a_type + : Diagnostics.An_export_default_must_reference_a_value_when_verbatimModuleSyntax_is_enabled_but_0_only_refers_to_a_type, + idText(id)); } } - checkObjectTypeForDuplicateDeclarations(node); - }); - forEach(getInterfaceBaseTypeNodes(node), heritageElement => { - if (!isEntityNameExpression(heritageElement.expression) || isOptionalChain(heritageElement.expression)) { - error(heritageElement.expression, Diagnostics.An_interface_can_only_extend_an_identifier_Slashqualified_name_with_optional_type_arguments); + else { + checkExpressionCached(id); // doesn't resolve, check as expression to mark as error } - checkTypeReferenceNode(heritageElement); - }); - forEach(node.members, checkSourceElement); + if (getEmitDeclarations(compilerOptions)) { + collectLinkedAliases(id, /*setVisibility*/ true); + } + } + else { + checkExpressionCached(node.expression); + } - addLazyDiagnostic(() => { - checkTypeForDuplicateIndexSignatures(node); - registerForUnusedIdentifiersCheck(node); - }); + if (isIllegalExportDefaultInCJS) { + error(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); + } + + 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) { + // Forbid export= in esm implementation files, and esm mode declaration files + if (moduleKind >= ModuleKind.ES2015 && + ((node.flags & NodeFlags.Ambient && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.ESNext) || + (!(node.flags & NodeFlags.Ambient) && getSourceFileOfNode(node).impliedNodeFormat !== ModuleKind.CommonJS))) { + // 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 && !(node.flags & NodeFlags.Ambient)) { + // system modules does not support export assignment + grammarErrorOnNode(node, Diagnostics.Export_assignment_is_not_supported_when_module_flag_is_system); + } + } } - function checkTypeAliasDeclaration(node: TypeAliasDeclaration) { - // Grammar checking - checkGrammarModifiers(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); + function hasExportedMembers(moduleSymbol: Symbol) { + return forEachEntry(moduleSymbol.exports!, (_, id) => id !== "export="); + } + + function checkExternalModuleExports(node: SourceFile | ModuleDeclaration) { + const moduleSymbol = getSymbolOfDeclaration(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 (declaration && !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.Enum)) { + return; + } + const exportedDeclarationsCount = countWhere(declarations, and(isNotOverloadAndNotAccessor, not(isInterfaceDeclaration))); + 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) { + if (!isDuplicatedCommonJSExport(declarations)) { + for (const declaration of declarations!) { + if (isNotOverload(declaration)) { + diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Cannot_redeclare_exported_variable_0, unescapeLeadingUnderscores(id))); + } + } + } + } + }); } - } - else { - checkSourceElement(node.type); - registerForUnusedIdentifiersCheck(node); + links.exportsChecked = true; } } - 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 isDuplicatedCommonJSExport(declarations: Declaration[] | undefined) { + return declarations + && declarations.length > 1 + && declarations.every(d => isInJSFile(d) && isAccessExpression(d) && (isExportsIdentifier(d.expression) || isModuleExportsAccessExpression(d.expression))); } - 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)) { - 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; + function checkSourceElement(node: Node | undefined): void { + if (node) { + const saveCurrentNode = currentNode; + currentNode = node; + instantiationCount = 0; + checkSourceElementWorker(node); + currentNode = saveCurrentNode; } - error(member.name, Diagnostics.Enum_member_must_have_initializer); - return undefined; } - function computeConstantValue(member: EnumMember): string | number | undefined { - const isConstEnum = isEnumConst(member.parent); - const initializer = member.initializer!; - const value = evaluate(initializer, member); - 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 (isConstEnum) { - error(initializer, Diagnostics.const_enum_member_initializers_must_be_constant_expressions); + function checkSourceElementWorker(node: Node): void { + if (canHaveJSDoc(node)) { + forEach(node.jsDoc, ({ comment, tags }) => { + checkJSDocCommentWorker(comment); + forEach(tags, tag => { + checkJSDocCommentWorker(tag.comment); + if (isInJSFile(node)) { + checkSourceElement(tag); + } + }); + }); } - else if (member.parent.flags & NodeFlags.Ambient) { - error(initializer, Diagnostics.In_ambient_enum_declarations_member_initializer_must_be_constant_expression); + + 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(); + } } - else { - checkTypeAssignableTo(checkExpression(initializer), numberType, initializer, Diagnostics.Type_0_is_not_assignable_to_type_1_as_required_for_computed_enum_member_values); + if (kind >= SyntaxKind.FirstStatement && kind <= SyntaxKind.LastStatement && canHaveFlowNode(node) && node.flowNode && !isReachableFlowNode(node.flowNode)) { + errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected); } - return value; - } - function evaluate(expr: Expression, location: Declaration): string | number | undefined { - switch (expr.kind) { - case SyntaxKind.PrefixUnaryExpression: - const value = evaluate((expr as PrefixUnaryExpression).operand, location); - if (typeof value === "number") { - switch ((expr as PrefixUnaryExpression).operator) { - case SyntaxKind.PlusToken: return value; - case SyntaxKind.MinusToken: return -value; - case SyntaxKind.TildeToken: return ~value; - } - } - break; - case SyntaxKind.BinaryExpression: - const left = evaluate((expr as BinaryExpression).left, location); - const right = evaluate((expr as BinaryExpression).right, location); - if (typeof left === "number" && typeof right === "number") { - switch ((expr as BinaryExpression).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 left === "number") && - (typeof right === "string" || typeof right === "number") && - (expr as BinaryExpression).operatorToken.kind === SyntaxKind.PlusToken) { - return "" + left + right; - } - break; - case SyntaxKind.StringLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - return (expr as StringLiteralLike).text; - case SyntaxKind.TemplateExpression: - return evaluateTemplateExpression(expr as TemplateExpression, location); - case SyntaxKind.NumericLiteral: - checkGrammarNumericLiteral(expr as NumericLiteral); - return +(expr as NumericLiteral).text; - case SyntaxKind.ParenthesizedExpression: - return evaluate((expr as ParenthesizedExpression).expression, location); - case SyntaxKind.Identifier: - if (isInfinityOrNaNString((expr as Identifier).escapedText)) { - return +((expr as Identifier).escapedText); - } + switch (kind) { + case SyntaxKind.TypeParameter: + return checkTypeParameter(node as TypeParameterDeclaration); + case SyntaxKind.Parameter: + return checkParameter(node as ParameterDeclaration); + case SyntaxKind.PropertyDeclaration: + return checkPropertyDeclaration(node as PropertyDeclaration); + case SyntaxKind.PropertySignature: + return checkPropertySignature(node as PropertySignature); + case SyntaxKind.ConstructorType: + case SyntaxKind.FunctionType: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + return checkSignatureDeclaration(node as SignatureDeclaration); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + return checkMethodDeclaration(node as MethodDeclaration | MethodSignature); + case SyntaxKind.ClassStaticBlockDeclaration: + return checkClassStaticBlockDeclaration(node as ClassStaticBlockDeclaration); + case SyntaxKind.Constructor: + return checkConstructorDeclaration(node as ConstructorDeclaration); + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return checkAccessorDeclaration(node as AccessorDeclaration); + case SyntaxKind.TypeReference: + return checkTypeReferenceNode(node as TypeReferenceNode); + case SyntaxKind.TypePredicate: + return checkTypePredicate(node as TypePredicateNode); + case SyntaxKind.TypeQuery: + return checkTypeQuery(node as TypeQueryNode); + case SyntaxKind.TypeLiteral: + return checkTypeLiteral(node as TypeLiteralNode); + case SyntaxKind.ArrayType: + return checkArrayType(node as ArrayTypeNode); + case SyntaxKind.TupleType: + return checkTupleType(node as TupleTypeNode); + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + return checkUnionOrIntersectionType(node as UnionOrIntersectionTypeNode); + case SyntaxKind.ParenthesizedType: + case SyntaxKind.OptionalType: + case SyntaxKind.RestType: + return checkSourceElement((node as ParenthesizedTypeNode | OptionalTypeNode | RestTypeNode).type); + case SyntaxKind.ThisType: + return checkThisType(node as ThisTypeNode); + case SyntaxKind.TypeOperator: + return checkTypeOperator(node as TypeOperatorNode); + case SyntaxKind.ConditionalType: + return checkConditionalType(node as ConditionalTypeNode); + case SyntaxKind.InferType: + return checkInferType(node as InferTypeNode); + case SyntaxKind.TemplateLiteralType: + return checkTemplateLiteralType(node as TemplateLiteralTypeNode); + case SyntaxKind.ImportType: + return checkImportType(node as ImportTypeNode); + case SyntaxKind.NamedTupleMember: + return checkNamedTupleMember(node as NamedTupleMember); + 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.JSDocLink: + case SyntaxKind.JSDocLinkCode: + case SyntaxKind.JSDocLinkPlain: + return checkJSDocLinkLikeTag(node as JSDocLink | JSDocLinkCode | JSDocLinkPlain); + 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.PropertyAccessExpression: - if (isEntityNameExpression(expr)) { - const symbol = resolveEntityName(expr, SymbolFlags.Value, /*ignoreErrors*/ true); - if (symbol) { - if (symbol.flags & SymbolFlags.EnumMember) { - return evaluateEnumMember(expr, symbol, location); - } - if (isConstVariable(symbol)) { - const declaration = symbol.valueDeclaration as VariableDeclaration | undefined; - if (declaration && !declaration.type && declaration.initializer && declaration !== location && isBlockScopedNameDeclaredBeforeUse(declaration, location)) { - return evaluate(declaration.initializer, declaration); - } - } - } - } - break; - case SyntaxKind.ElementAccessExpression: - const root = (expr as ElementAccessExpression).expression; - if (isEntityNameExpression(root) && isStringLiteralLike((expr as ElementAccessExpression).argumentExpression)) { - const rootSymbol = resolveEntityName(root, SymbolFlags.Value, /*ignoreErrors*/ true); - if (rootSymbol && rootSymbol.flags & SymbolFlags.Enum) { - const name = escapeLeadingUnderscores(((expr as ElementAccessExpression).argumentExpression as StringLiteralLike).text); - const member = rootSymbol.exports!.get(name); - if (member) { - return evaluateEnumMember(expr, member, location); - } - } - } - break; + 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.JSDocPublicTag: + case SyntaxKind.JSDocProtectedTag: + case SyntaxKind.JSDocPrivateTag: + return checkJSDocAccessibilityModifiers(node as JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag); + case SyntaxKind.JSDocSatisfiesTag: + return checkJSDocSatisfiesTag(node as JSDocSatisfiesTag); + case SyntaxKind.IndexedAccessType: + return checkIndexedAccessType(node as IndexedAccessTypeNode); + case SyntaxKind.MappedType: + return checkMappedType(node as MappedTypeNode); + case SyntaxKind.FunctionDeclaration: + return checkFunctionDeclaration(node as FunctionDeclaration); + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + return checkBlock(node as Block); + case SyntaxKind.VariableStatement: + return checkVariableStatement(node as VariableStatement); + case SyntaxKind.ExpressionStatement: + return checkExpressionStatement(node as ExpressionStatement); + case SyntaxKind.IfStatement: + return checkIfStatement(node as IfStatement); + case SyntaxKind.DoStatement: + return checkDoStatement(node as DoStatement); + case SyntaxKind.WhileStatement: + return checkWhileStatement(node as WhileStatement); + case SyntaxKind.ForStatement: + return checkForStatement(node as ForStatement); + case SyntaxKind.ForInStatement: + return checkForInStatement(node as ForInStatement); + case SyntaxKind.ForOfStatement: + return checkForOfStatement(node as ForOfStatement); + case SyntaxKind.ContinueStatement: + case SyntaxKind.BreakStatement: + return checkBreakOrContinueStatement(node as BreakOrContinueStatement); + case SyntaxKind.ReturnStatement: + return checkReturnStatement(node as ReturnStatement); + case SyntaxKind.WithStatement: + return checkWithStatement(node as WithStatement); + case SyntaxKind.SwitchStatement: + return checkSwitchStatement(node as SwitchStatement); + case SyntaxKind.LabeledStatement: + return checkLabeledStatement(node as LabeledStatement); + case SyntaxKind.ThrowStatement: + return checkThrowStatement(node as ThrowStatement); + case SyntaxKind.TryStatement: + return checkTryStatement(node as TryStatement); + case SyntaxKind.VariableDeclaration: + return checkVariableDeclaration(node as VariableDeclaration); + case SyntaxKind.BindingElement: + return checkBindingElement(node as BindingElement); + case SyntaxKind.ClassDeclaration: + return checkClassDeclaration(node as ClassDeclaration); + case SyntaxKind.InterfaceDeclaration: + return checkInterfaceDeclaration(node as InterfaceDeclaration); + case SyntaxKind.TypeAliasDeclaration: + return checkTypeAliasDeclaration(node as TypeAliasDeclaration); + case SyntaxKind.EnumDeclaration: + return checkEnumDeclaration(node as EnumDeclaration); + case SyntaxKind.ModuleDeclaration: + return checkModuleDeclaration(node as ModuleDeclaration); + case SyntaxKind.ImportDeclaration: + return checkImportDeclaration(node as ImportDeclaration); + case SyntaxKind.ImportEqualsDeclaration: + return checkImportEqualsDeclaration(node as ImportEqualsDeclaration); + case SyntaxKind.ExportDeclaration: + return checkExportDeclaration(node as ExportDeclaration); + case SyntaxKind.ExportAssignment: + return checkExportAssignment(node as ExportAssignment); + case SyntaxKind.EmptyStatement: + case SyntaxKind.DebuggerStatement: + checkGrammarStatementInAmbientContext(node); + return; + case SyntaxKind.MissingDeclaration: + return checkMissingDeclaration(node); } - return undefined; } - function evaluateEnumMember(expr: Expression, symbol: Symbol, location: Declaration) { - const declaration = symbol.valueDeclaration; - if (!declaration || declaration === location) { - error(expr, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(symbol)); - return undefined; - } - if (!isBlockScopedNameDeclaredBeforeUse(declaration, location)) { - error(expr, Diagnostics.A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums); - return 0; + function checkJSDocCommentWorker(node: string | readonly JSDocComment[] | undefined) { + if (isArray(node)) { + forEach(node, tag => { + if (isJSDocLinkLike(tag)) { + checkSourceElement(tag); + } + }); } - return getEnumMemberValue(declaration as EnumMember); } - function evaluateTemplateExpression(expr: TemplateExpression, location: Declaration) { - let result = expr.head.text; - for (const span of expr.templateSpans) { - const value = evaluate(span.expression, location); - if (value === undefined) { - return undefined; + function checkJSDocTypeIsInJsFile(node: Node): void { + if (!isInJSFile(node)) { + if (isJSDocNonNullableType(node) || isJSDocNullableType(node)) { + const token = tokenToString(isJSDocNonNullableType(node) ? SyntaxKind.ExclamationToken : SyntaxKind.QuestionToken); + const diagnostic = node.postfix + ? Diagnostics._0_at_the_end_of_a_type_is_not_valid_TypeScript_syntax_Did_you_mean_to_write_1 + : Diagnostics._0_at_the_start_of_a_type_is_not_valid_TypeScript_syntax_Did_you_mean_to_write_1; + const typeNode = node.type; + const type = getTypeFromTypeNode(typeNode); + grammarErrorOnNode(node, diagnostic, token, typeToString( + isJSDocNullableType(node) && !(type === neverType || type === voidType) + ? getUnionType(append([type, undefinedType], node.postfix ? undefined : nullType)) : type)); + } + else { + grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); } - result += value; - result += span.literal.text; } - return result; - } - - function checkEnumDeclaration(node: EnumDeclaration) { - addLazyDiagnostic(() => checkEnumDeclarationWorker(node)); } - function checkEnumDeclarationWorker(node: EnumDeclaration) { - // Grammar checking - checkGrammarModifiers(node); + function checkJSDocVariadicType(node: JSDocVariadicType): void { + checkJSDocTypeIsInJsFile(node); + checkSourceElement(node.type); - checkCollisionsForDeclarationName(node, node.name); - checkExportsOnMergedDeclarations(node); - node.members.forEach(checkEnumMember); + // 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; + } - computeEnumMemberValues(node); + if (!isJSDocTypeExpression(parent)) { + error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); + } - // 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 = getSymbolOfDeclaration(node); - const firstDeclaration = getDeclarationOfKind(enumSymbol, node.kind); - if (node === firstDeclaration) { - if (enumSymbol.declarations && 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); - } - }); - } + const paramTag = node.parent.parent; + if (!isJSDocParameterTag(paramTag)) { + error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); + return; + } - 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 param = getParameterSymbolFromJSDoc(paramTag); + if (!param) { + // We will error in `checkJSDocParameterTag`. + return; + } - const enumDeclaration = declaration as EnumDeclaration; - if (!enumDeclaration.members.length) { - return false; - } + const host = getHostSignatureFromJSDoc(paramTag); + if (!host || last(host.parameters).symbol !== param) { + error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); + } + } - 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 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); + const isCallbackTag = isJSDocCallbackTag(paramTag.parent.parent); + if (host || isCallbackTag) { + /* + 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 = isCallbackTag + ? lastOrUndefined((paramTag.parent.parent as unknown as JSDocCallbackTag).typeExpression.parameters) + : 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 checkEnumMember(node: EnumMember) { - if (isPrivateIdentifier(node.name)) { - error(node, Diagnostics.An_enum_member_cannot_be_named_with_a_private_identifier); + // 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 ||= new Set(); + links.deferredNodes.add(node); } - if (node.initializer) { - checkExpression(node.initializer); + else { + Debug.assert(!links.deferredNodes, "A type-checked file should have no deferred nodes."); } } - function getFirstNonAmbientClassOrFunctionDeclaration(symbol: Symbol): Declaration | undefined { - const declarations = symbol.declarations; - if (declarations) { - for (const declaration of declarations) { - if ((declaration.kind === SyntaxKind.ClassDeclaration || - (declaration.kind === SyntaxKind.FunctionDeclaration && nodeIsPresent((declaration as FunctionLikeDeclaration).body))) && - !(declaration.flags & NodeFlags.Ambient)) { - return declaration; - } - } + function checkDeferredNodes(context: SourceFile) { + const links = getNodeLinks(context); + if (links.deferredNodes) { + links.deferredNodes.forEach(checkDeferredNode); } - return undefined; + links.deferredNodes = undefined; } - function inSameLexicalScope(node1: Node, node2: Node) { - const container1 = getEnclosingBlockScopeContainer(node1); - const container2 = getEnclosingBlockScopeContainer(node2); - if (isGlobalSourceFile(container1)) { - return isGlobalSourceFile(container2); + function checkDeferredNode(node: Node) { + tracing?.push(tracing.Phase.Check, "checkDeferredNode", { kind: node.kind, pos: node.pos, end: node.end, path: (node as TracingNode).tracingPath }); + 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 as FunctionExpression); + break; + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + checkAccessorDeclaration(node as AccessorDeclaration); + break; + case SyntaxKind.ClassExpression: + checkClassExpressionDeferred(node as ClassExpression); + break; + case SyntaxKind.TypeParameter: + checkTypeParameterDeferred(node as TypeParameterDeclaration); + break; + case SyntaxKind.JsxSelfClosingElement: + checkJsxSelfClosingElementDeferred(node as JsxSelfClosingElement); + break; + case SyntaxKind.JsxElement: + checkJsxElementDeferred(node as JsxElement); + break; + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.ParenthesizedExpression: + checkAssertionDeferred(node as AssertionExpression | JSDocTypeAssertion); } - else if (isGlobalSourceFile(container2)) { + currentNode = saveCurrentNode; + tracing?.pop(); + } + + function checkSourceFile(node: SourceFile) { + tracing?.push(tracing.Phase.Check, "checkSourceFile", { path: node.path }, /*separateBeginAndEnd*/ true); + performance.mark("beforeCheck"); + checkSourceFileWorker(node); + performance.mark("afterCheck"); + performance.measure("Check", "beforeCheck", "afterCheck"); + tracing?.pop(); + } + + function unusedIsError(kind: UnusedKind, isAmbient: boolean): boolean { + if (isAmbient) { return false; } - else { - return container1 === container2; + switch (kind) { + case UnusedKind.Local: + return !!compilerOptions.noUnusedLocals; + case UnusedKind.Parameter: + return !!compilerOptions.noUnusedParameters; + default: + return Debug.assertNever(kind); } } - function checkModuleDeclaration(node: ModuleDeclaration) { - if (node.body) { - checkSourceElement(node.body); - if (!isGlobalScopeAugmentation(node)) { - registerForUnusedIdentifiersCheck(node); - } - } + function getPotentiallyUnusedIdentifiers(sourceFile: SourceFile): readonly PotentiallyUnusedIdentifier[] { + return allPotentiallyUnusedIdentifiers.get(sourceFile.path) || emptyArray; + } - addLazyDiagnostic(checkModuleDeclarationDiagnostics); + // 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; + } - function checkModuleDeclarationDiagnostics() { // 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); - } + checkGrammarSourceFile(node); - const isAmbientExternalModule: boolean = 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_at_the_top_level_of_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; + clear(potentialThisCollisions); + clear(potentialNewTargetCollisions); + clear(potentialWeakMapSetCollisions); + clear(potentialReflectCollisions); + clear(potentialUnusedRenamedBindingElementsInTypes); + + forEach(node.statements, checkSourceElement); + checkSourceElement(node.endOfFileToken); + + checkDeferredNodes(node); + + if (isExternalOrCommonJsModule(node)) { + registerForUnusedIdentifiersCheck(node); } - if (!checkGrammarModifiers(node)) { - if (!inAmbientContext && node.name.kind === SyntaxKind.StringLiteral) { - grammarErrorOnNode(node.name, Diagnostics.Only_ambient_modules_can_use_quoted_names); + addLazyDiagnostic(() => { + // This relies on the results of other lazy diagnostics, so must be computed after them + 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 (!node.isDeclarationFile) { + checkPotentialUncheckedRenamedBindingElementsInTypes(); + } + }); + + if (compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && + !node.isDeclarationFile && + isExternalModule(node) + ) { + checkImportsForTypeOnlyConversion(node); } - if (isIdentifier(node.name)) { - checkCollisionsForDeclarationName(node, node.name); + if (isExternalOrCommonJsModule(node)) { + checkExternalModuleExports(node); } - checkExportsOnMergedDeclarations(node); - const symbol = getSymbolOfDeclaration(node); + if (potentialThisCollisions.length) { + forEach(potentialThisCollisions, checkIfThisIsCapturedInEnclosingScope); + clear(potentialThisCollisions); + } - // The following checks only apply on a non-ambient instantiated module declaration. - if (symbol.flags & SymbolFlags.ValueModule - && !inAmbientContext - && isInstantiatedModule(node, shouldPreserveConstEnums(compilerOptions)) - ) { - if (getIsolatedModules(compilerOptions) && !getSourceFileOfNode(node).externalModuleIndicator) { - // This could be loosened a little if needed. The only problem we are trying to avoid is unqualified - // references to namespace members declared in other files. But use of namespaces is discouraged anyway, - // so for now we will just not allow them in scripts, which is the only place they can merge cross-file. - error(node.name, Diagnostics.Namespaces_are_not_allowed_in_global_script_files_when_0_is_enabled_If_this_file_is_not_intended_to_be_a_global_script_set_moduleDetection_to_force_or_add_an_empty_export_statement, isolatedModulesLikeFlagName); - } - if (symbol.declarations?.length! > 1) { - 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 (potentialNewTargetCollisions.length) { + forEach(potentialNewTargetCollisions, checkIfNewTargetIsCapturedInEnclosingScope); + clear(potentialNewTargetCollisions); + } - // 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 (compilerOptions.verbatimModuleSyntax && - node.parent.kind === SyntaxKind.SourceFile && - (moduleKind === ModuleKind.CommonJS || node.parent.impliedNodeFormat === ModuleKind.CommonJS) - ) { - const exportModifier = node.modifiers?.find(m => m.kind === SyntaxKind.ExportKeyword); - if (exportModifier) { - error(exportModifier, Diagnostics.A_top_level_export_modifier_cannot_be_used_on_value_declarations_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); - } - } + if (potentialWeakMapSetCollisions.length) { + forEach(potentialWeakMapSetCollisions, checkWeakMapSetCollision); + clear(potentialWeakMapSetCollisions); } - 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 || (getSymbolOfDeclaration(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 (potentialReflectCollisions.length) { + forEach(potentialReflectCollisions, checkReflectCollision); + clear(potentialReflectCollisions); } - } - } - 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 as VariableStatement).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 as VariableDeclaration | BindingElement).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; - } - break; + links.flags |= NodeCheckFlags.TypeChecked; } } - 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 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 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; - } - } - if (!isImportEqualsDeclaration(node) && node.assertClause) { - let hasError = false; - for (const clause of node.assertClause.elements) { - if (!isStringLiteral(clause.value)) { - hasError = true; - error(clause.value, Diagnostics.Import_assertion_values_must_be_string_literal_expressions); - } - } - return !hasError; + function ensurePendingDiagnosticWorkComplete() { + // Invoke any existing lazy diagnostics to add them, clear the backlog of diagnostics + for (const cb of deferredDiagnosticsCallbacks) { + cb(); } - return true; + deferredDiagnosticsCallbacks = []; } - function checkAliasSymbol(node: AliasDeclarationNode) { - let symbol = getSymbolOfDeclaration(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); + function checkSourceFileWithEagerDiagnostics(sourceFile: SourceFile) { + ensurePendingDiagnosticWorkComplete(); + // then setup diagnostics for immediate invocation (as we are about to collect them, and + // this avoids the overhead of longer-lived callbacks we don't need to allocate) + // This also serves to make the shift to possibly lazy diagnostics transparent to serial command-line scenarios + // (as in those cases, all the diagnostics will still be computed as the appropriate place in the tree, + // thus much more likely retaining the same union ordering as before we had lazy diagnostics) + const oldAddLazyDiagnostics = addLazyDiagnostic; + addLazyDiagnostic = cb => cb(); + checkSourceFile(sourceFile); + addLazyDiagnostic = oldAddLazyDiagnostics; + } - // A type-only import/export will already have a grammar error in a JS file, so no need to issue more errors within - if (isInJSFile(node) && !(target.flags & SymbolFlags.Value) && !isTypeOnlyImportOrExportDeclaration(node)) { - const errorNode = - isImportOrExportSpecifier(node) ? node.propertyName || node.name : - isNamedDeclaration(node) ? node.name : - node; + function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] { + if (sourceFile) { + ensurePendingDiagnosticWorkComplete(); + // 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; - Debug.assert(node.kind !== SyntaxKind.NamespaceExport); - if (node.kind === SyntaxKind.ExportSpecifier) { - const diag = error(errorNode, Diagnostics.Types_cannot_appear_in_export_declarations_in_JavaScript_files); - const alreadyExportedSymbol = getSourceFileOfNode(node).symbol?.exports?.get((node.propertyName || node.name).escapedText); - if (alreadyExportedSymbol === target) { - const exportingDeclaration = alreadyExportedSymbol.declarations?.find(isJSDocNode); - if (exportingDeclaration) { - addRelatedInfo(diag, createDiagnosticForNode( - exportingDeclaration, - Diagnostics._0_is_automatically_exported_here, - unescapeLeadingUnderscores(alreadyExportedSymbol.escapedName))); - } - } - } - else { - Debug.assert(node.kind !== SyntaxKind.VariableDeclaration); - const importDeclaration = findAncestor(node, or(isImportDeclaration, isImportEqualsDeclaration)); - const moduleSpecifier = (importDeclaration && tryGetModuleSpecifierFromDeclaration(importDeclaration)?.text) ?? "..."; - const importedIdentifier = unescapeLeadingUnderscores(isIdentifier(errorNode) ? errorNode.escapedText : symbol.escapedName); - error( - errorNode, - Diagnostics._0_is_a_type_and_cannot_be_imported_in_JavaScript_files_Use_1_in_a_JSDoc_type_annotation, - importedIdentifier, - `import("${moduleSpecifier}").${importedIdentifier}`); - } - return; - } + checkSourceFileWithEagerDiagnostics(sourceFile); - const targetFlags = getAllSymbolFlags(target); - 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 (targetFlags & 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)); + 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); } - - if (getIsolatedModules(compilerOptions) - && !isTypeOnlyImportOrExportDeclaration(node) - && !(node.flags & NodeFlags.Ambient)) { - const typeOnlyAlias = getTypeOnlyAliasDeclaration(symbol); - const isType = !(targetFlags & SymbolFlags.Value); - if (isType || typeOnlyAlias) { - switch (node.kind) { - case SyntaxKind.ImportClause: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ImportEqualsDeclaration: { - if (compilerOptions.preserveValueImports || compilerOptions.verbatimModuleSyntax) { - Debug.assertIsDefined(node.name, "An ImportClause with a symbol should have a name"); - const message = compilerOptions.verbatimModuleSyntax && isInternalModuleImportEqualsDeclaration(node) - ? Diagnostics.An_import_alias_cannot_resolve_to_a_type_or_type_only_declaration_when_verbatimModuleSyntax_is_enabled - : isType - ? compilerOptions.verbatimModuleSyntax - ? Diagnostics._0_is_a_type_and_must_be_imported_using_a_type_only_import_when_verbatimModuleSyntax_is_enabled - : Diagnostics._0_is_a_type_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled - : compilerOptions.verbatimModuleSyntax - ? Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_verbatimModuleSyntax_is_enabled - : Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_imported_using_a_type_only_import_when_preserveValueImports_and_isolatedModules_are_both_enabled; - const name = idText(node.kind === SyntaxKind.ImportSpecifier ? node.propertyName || node.name : node.name); - addTypeOnlyDeclarationRelatedInfo( - error(node, message, name), - isType ? undefined : typeOnlyAlias, - name - ); - } - if (isType && node.kind === SyntaxKind.ImportEqualsDeclaration && hasEffectiveModifier(node, ModifierFlags.Export)) { - error(node, Diagnostics.Cannot_use_export_import_on_a_type_or_type_only_namespace_when_0_is_enabled, isolatedModulesLikeFlagName); - } - break; - } - case SyntaxKind.ExportSpecifier: { - // Don't allow re-exporting an export that will be elided when `--isolatedModules` is set. - // The exception is that `import type { A } from './a'; export { A }` is allowed - // because single-file analysis can determine that the export should be dropped. - if (compilerOptions.verbatimModuleSyntax || getSourceFileOfNode(typeOnlyAlias) !== getSourceFileOfNode(node)) { - const name = idText(node.propertyName || node.name); - const diagnostic = isType - ? error(node, Diagnostics.Re_exporting_a_type_when_0_is_enabled_requires_using_export_type, isolatedModulesLikeFlagName) - : error(node, Diagnostics._0_resolves_to_a_type_only_declaration_and_must_be_re_exported_using_a_type_only_re_export_when_1_is_enabled, name, isolatedModulesLikeFlagName); - addTypeOnlyDeclarationRelatedInfo(diagnostic, isType ? undefined : typeOnlyAlias, name); - break; - } - } - } - } - - if (compilerOptions.verbatimModuleSyntax && - node.kind !== SyntaxKind.ImportEqualsDeclaration && - !isInJSFile(node) && - (moduleKind === ModuleKind.CommonJS || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) - ) { - error(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); - } + 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); } - if (isImportSpecifier(node)) { - const targetSymbol = checkDeprecatedAliasedSymbol(symbol, node); - if (isDeprecatedAliasedSymbol(targetSymbol) && targetSymbol.declarations) { - addDeprecatedSuggestion(node, targetSymbol.declarations, targetSymbol.escapedName as string); - } - } + return semanticDiagnostics; } + + // Global diagnostics are always added when a file is not provided to + // getDiagnostics + forEach(host.getSourceFiles(), checkSourceFileWithEagerDiagnostics); + return diagnostics.getDiagnostics(); } - function isDeprecatedAliasedSymbol(symbol: Symbol) { - return !!symbol.declarations && every(symbol.declarations, d => !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated)); + function getGlobalDiagnostics(): Diagnostic[] { + ensurePendingDiagnosticWorkComplete(); + return diagnostics.getGlobalDiagnostics(); } - function checkDeprecatedAliasedSymbol(symbol: Symbol, location: Node) { - if (!(symbol.flags & SymbolFlags.Alias)) return symbol; - - const targetSymbol = resolveAlias(symbol); - if (targetSymbol === unknownSymbol) return targetSymbol; + // Language service support - while (symbol.flags & SymbolFlags.Alias) { - const target = getImmediateAliasedSymbol(symbol); - if (target) { - if (target === targetSymbol) break; - if (target.declarations && length(target.declarations)) { - if (isDeprecatedAliasedSymbol(target)) { - addDeprecatedSuggestion(location, target.declarations, target.escapedName as string); - break; - } - else { - if (symbol === targetSymbol) break; - symbol = target; - } - } - } - else { - break; - } + 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 []; } - return targetSymbol; - } - function checkImportBinding(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier) { - checkCollisionsForDeclarationName(node, node.name); - checkAliasSymbol(node); - if (node.kind === SyntaxKind.ImportSpecifier && - idText(node.propertyName || node.name) === "default" && - getESModuleInterop(compilerOptions) && - moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); - } - } + const symbols = createSymbolTable(); + let isStaticSymbol = false; - function checkAssertClause(declaration: ImportDeclaration | ExportDeclaration) { - if (declaration.assertClause) { - const validForTypeAssertions = isExclusivelyTypeOnlyImportOrExport(declaration); - const override = getResolutionModeOverrideForClause(declaration.assertClause, validForTypeAssertions ? grammarErrorOnNode : undefined); - if (validForTypeAssertions && override) { - if (!isNightly()) { - grammarErrorOnNode(declaration.assertClause, Diagnostics.resolution_mode_assertions_are_unstable_Use_nightly_TypeScript_to_silence_this_error_Try_updating_with_npm_install_D_typescript_next); + populateSymbols(); + + symbols.delete(InternalSymbolName.This); // Not a symbol, a keyword + return symbolsToArray(symbols); + + function populateSymbols() { + while (location) { + if (canHaveLocals(location) && location.locals && !isGlobalSourceFile(location)) { + copySymbols(location.locals, meaning); } - if (getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Node16 && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeNext) { - return grammarErrorOnNode(declaration.assertClause, Diagnostics.resolution_mode_assertions_are_only_supported_when_moduleResolution_is_node16_or_nodenext); + switch (location.kind) { + case SyntaxKind.SourceFile: + if (!isExternalModule(location as SourceFile)) break; + // falls through + case SyntaxKind.ModuleDeclaration: + copyLocallyVisibleExportSymbols(getSymbolOfDeclaration(location as ModuleDeclaration | SourceFile).exports!, meaning & SymbolFlags.ModuleMember); + break; + case SyntaxKind.EnumDeclaration: + copySymbols(getSymbolOfDeclaration(location as EnumDeclaration).exports!, meaning & SymbolFlags.EnumMember); + break; + case SyntaxKind.ClassExpression: + const className = (location as ClassExpression).name; + if (className) { + copySymbol((location as ClassExpression).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 (!isStaticSymbol) { + copySymbols(getMembersOfSymbol(getSymbolOfDeclaration(location as ClassDeclaration | InterfaceDeclaration)), meaning & SymbolFlags.Type); + } + break; + case SyntaxKind.FunctionExpression: + const funcName = (location as FunctionExpression).name; + if (funcName) { + copySymbol((location as FunctionExpression).symbol, meaning); + } + break; } - return; // Other grammar checks do not apply to type-only imports with resolution mode assertions - } - const mode = (moduleKind === ModuleKind.NodeNext) && declaration.moduleSpecifier && getUsageModeForExpression(declaration.moduleSpecifier); - if (mode !== ModuleKind.ESNext && moduleKind !== ModuleKind.ESNext) { - return grammarErrorOnNode(declaration.assertClause, - moduleKind === ModuleKind.NodeNext - ? Diagnostics.Import_assertions_are_not_allowed_on_statements_that_transpile_to_commonjs_require_calls - : Diagnostics.Import_assertions_are_only_supported_when_the_module_option_is_set_to_esnext_or_nodenext); - } + if (introducesArgumentsExoticObject(location)) { + copySymbol(argumentsSymbol, meaning); + } - if (isImportDeclaration(declaration) ? declaration.importClause?.isTypeOnly : declaration.isTypeOnly) { - return grammarErrorOnNode(declaration.assertClause, Diagnostics.Import_assertions_cannot_be_used_with_type_only_imports_or_exports); + isStaticSymbol = isStatic(location); + location = location.parent; } - if (override) { - return grammarErrorOnNode(declaration.assertClause, Diagnostics.resolution_mode_can_only_be_set_for_type_only_imports); - } + copySymbols(globals, meaning); } - } - function checkImportDeclaration(node: ImportDeclaration) { - if (checkGrammarModuleElementContext(node, isInJSFile(node) ? Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_module : Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_namespace_or_module)) { - // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. - return; - } - if (!checkGrammarModifiers(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); + /** + * 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); } - if (importClause.namedBindings) { - if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { - checkImportBinding(importClause.namedBindings); - if (moduleKind !== ModuleKind.System && (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && getESModuleInterop(compilerOptions)) { - // import * as ns from "foo"; - checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); - } - } - else { - const moduleExisted = resolveExternalModuleName(node, node.moduleSpecifier); - if (moduleExisted) { - forEach(importClause.namedBindings.elements, checkImportBinding); - } - } + } + if (companionSymbolCache.has(symbol)) { + const id = symbol.escapedName; + if (!symbols.has(id)) { + symbols.set(id, symbol); } } - } - checkAssertClause(node); - if (node.isTsPlusGlobal) { - const location = (node.moduleSpecifier as StringLiteral).text; - if (pathIsRelative(location)) { - error(node.moduleSpecifier, Diagnostics.Global_import_path_cannot_be_relative); + if (symbol.declarations && symbol.declarations[0] && isImportSpecifier(symbol.declarations[0])) { + const originalSymbol = getTargetOfImportSpecifier(symbol.declarations[0], false); + if (originalSymbol && companionSymbolCache.has(originalSymbol)) { + const id = symbol.escapedName; + if (!symbols.has(id)) { + symbols.set(id, symbol); + } + } } } - } - function checkImportEqualsDeclaration(node: ImportEqualsDeclaration) { - if (checkGrammarModuleElementContext(node, isInJSFile(node) ? Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_module : Diagnostics.An_import_declaration_can_only_be_used_at_the_top_level_of_a_namespace_or_module)) { - // If we hit an import declaration in an illegal context, just bail out to avoid cascading errors. - return; + function copySymbols(source: SymbolTable, meaning: SymbolFlags): void { + if (meaning) { + source.forEach(symbol => { + copySymbol(symbol, meaning); + }); + } } - checkGrammarModifiers(node); - if (isInternalModuleImportEqualsDeclaration(node) || checkExternalImportOrExportDeclaration(node)) { - checkImportBinding(node); - if (hasSyntacticModifier(node, ModifierFlags.Export)) { - markExportAsReferenced(node); - } - if (node.moduleReference.kind !== SyntaxKind.ExternalModuleReference) { - const target = resolveAlias(getSymbolOfDeclaration(node)); - if (target !== unknownSymbol) { - const targetFlags = getAllSymbolFlags(target); - if (targetFlags & 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 (targetFlags & SymbolFlags.Type) { - checkTypeNameIsReserved(node.name, Diagnostics.Import_name_cannot_be_0); + function copyLocallyVisibleExportSymbols(source: SymbolTable, meaning: SymbolFlags): void { + if (meaning) { + source.forEach(symbol => { + // Similar condition as in `resolveNameHelper` + if (!getDeclarationOfKind(symbol, SyntaxKind.ExportSpecifier) && !getDeclarationOfKind(symbol, SyntaxKind.NamespaceExport)) { + copySymbol(symbol, meaning); } - } - if (node.isTypeOnly) { - grammarErrorOnNode(node, Diagnostics.An_import_alias_cannot_use_import_type); - } - } - else { - if (moduleKind >= ModuleKind.ES2015 && getSourceFileOfNode(node).impliedNodeFormat === undefined && !node.isTypeOnly && !(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, isInJSFile(node) ? Diagnostics.An_export_declaration_can_only_be_used_at_the_top_level_of_a_module : Diagnostics.An_export_declaration_can_only_be_used_at_the_top_level_of_a_namespace_or_module)) { - // If we hit an export in an illegal context, just bail out to avoid cascading errors. - return; + function isTypeDeclarationName(name: Node): boolean { + return name.kind === SyntaxKind.Identifier && + isTypeDeclaration(name.parent) && + getNameOfDeclaration(name.parent) === name; + } + + // 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; } - if (!checkGrammarModifiers(node) && hasSyntacticModifiers(node)) { - grammarErrorOnFirstToken(node, Diagnostics.An_export_declaration_cannot_have_modifiers); + return node.parent.kind === SyntaxKind.TypeReference; + } + + function isInNameOfExpressionWithTypeArguments(node: Node): boolean { + while (node.parent.kind === SyntaxKind.PropertyAccessExpression) { + node = node.parent; } - if (node.moduleSpecifier && node.exportClause && isNamedExports(node.exportClause) && length(node.exportClause.elements) && languageVersion === ScriptTarget.ES3) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.CreateBinding); + return node.parent.kind === SyntaxKind.ExpressionWithTypeArguments; + } + + function forEachEnclosingClass(node: Node, callback: (node: ClassLikeDeclaration) => T | undefined): T | undefined { + let result: T | undefined; + let containingClass = getContainingClass(node); + while (containingClass) { + if (result = callback(containingClass)) break; + containingClass = getContainingClass(containingClass); } - 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); - } + return result; + } + + function isNodeUsedDuringClassInitialization(node: Node) { + return !!findAncestor(node, element => { + if (isConstructorDeclaration(element) && nodeIsPresent(element.body) || isPropertyDeclaration(element)) { + return true; } - 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 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS)) { - 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 (getESModuleInterop(compilerOptions)) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); - } - } - else { - // export * from "foo" - checkExternalEmitHelpers(node, ExternalEmitHelpers.ExportStar); - } - } + else if (isClassLike(element) || isFunctionLikeDeclaration(element)) { + return "quit"; } - } - checkAssertClause(node); + + return false; + }); } - function checkGrammarExportDeclaration(node: ExportDeclaration): boolean { - if (node.isTypeOnly && node.exportClause?.kind === SyntaxKind.NamedExports) { - return checkGrammarNamedImportsOrExports(node.exportClause); - } - return false; + function isNodeWithinClass(node: Node, classDeclaration: ClassLikeDeclaration) { + return !!forEachEnclosingClass(node, n => n === classDeclaration); } - 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); + function getLeftSideOfImportEqualsOrExportAssignment(nodeOnRightSide: EntityName): ImportEqualsDeclaration | ExportAssignment | undefined { + while (nodeOnRightSide.parent.kind === SyntaxKind.QualifiedName) { + nodeOnRightSide = nodeOnRightSide.parent as QualifiedName; } - return !isInAppropriateContext; - } - function importClauseContainsReferencedImport(importClause: ImportClause) { - return forEachImportClauseDeclaration(importClause, declaration => { - return !!getSymbolOfDeclaration(declaration).isReferenced; - }); - } + if (nodeOnRightSide.parent.kind === SyntaxKind.ImportEqualsDeclaration) { + return (nodeOnRightSide.parent as ImportEqualsDeclaration).moduleReference === nodeOnRightSide ? nodeOnRightSide.parent as ImportEqualsDeclaration : undefined; + } - function importClauseContainsConstEnumUsedAsValue(importClause: ImportClause) { - return forEachImportClauseDeclaration(importClause, declaration => { - return !!getSymbolLinks(getSymbolOfDeclaration(declaration)).constEnumReferenced; - }); + if (nodeOnRightSide.parent.kind === SyntaxKind.ExportAssignment) { + return (nodeOnRightSide.parent as ExportAssignment).expression === nodeOnRightSide as Node ? nodeOnRightSide.parent as ExportAssignment : undefined; + } + + return undefined; } - function canConvertImportDeclarationToTypeOnly(statement: Statement) { - return isImportDeclaration(statement) && - statement.importClause && - !statement.importClause.isTypeOnly && - importClauseContainsReferencedImport(statement.importClause) && - !isReferencedAliasDeclaration(statement.importClause, /*checkChildren*/ true) && - !importClauseContainsConstEnumUsedAsValue(statement.importClause); + function isInRightSideOfImportOrExportAssignment(node: EntityName) { + return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined; } - function canConvertImportEqualsDeclarationToTypeOnly(statement: Statement) { - return isImportEqualsDeclaration(statement) && - isExternalModuleReference(statement.moduleReference) && - !statement.isTypeOnly && - getSymbolOfDeclaration(statement).isReferenced && - !isReferencedAliasDeclaration(statement, /*checkChildren*/ false) && - !getSymbolLinks(getSymbolOfDeclaration(statement)).constEnumReferenced; + 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 getSymbolOfDeclaration(entityName.parent.parent as BinaryExpression); + } } - function checkImportsForTypeOnlyConversion(sourceFile: SourceFile) { - if (!canCollectSymbolAliasAccessabilityData) { - return; + function isImportTypeQualifierPart(node: EntityName): ImportTypeNode | undefined { + let parent = node.parent; + while (isQualifiedName(parent)) { + node = parent; + parent = parent.parent; } - for (const statement of sourceFile.statements) { - if (canConvertImportDeclarationToTypeOnly(statement) || canConvertImportEqualsDeclarationToTypeOnly(statement)) { - error( - statement, - Diagnostics.This_import_is_never_used_as_a_value_and_must_use_import_type_because_importsNotUsedAsValues_is_set_to_error); - } + if (parent && parent.kind === SyntaxKind.ImportType && (parent as ImportTypeNode).qualifier === node) { + return parent as ImportTypeNode; } + return undefined; } - 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 || symbol.declarations && isGlobalSourceFile(getDeclarationContainer(symbol.declarations[0])))) { - error(exportedName, Diagnostics.Cannot_export_0_Only_local_declarations_can_be_exported_from_a_module, idText(exportedName)); - } - else { - if (!node.isTypeOnly && !node.parent.parent.isTypeOnly) { - markExportAsReferenced(node); - } - const target = symbol && (symbol.flags & SymbolFlags.Alias ? resolveAlias(symbol) : symbol); - if (!target || getAllSymbolFlags(target) & SymbolFlags.Value) { - checkExpressionCached(node.propertyName || node.name); + function isThisPropertyAndThisTyped(node: PropertyAccessExpression) { + if (node.expression.kind === SyntaxKind.ThisKeyword) { + const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); + if (isFunctionLike(container)) { + const containingLiteral = getContainingObjectLiteral(container); + if (containingLiteral) { + const contextualType = getApparentTypeOfContextualType(containingLiteral, /*contextFlags*/ undefined); + const type = contextualType && getThisTypeFromContextualType(contextualType); + return type && !isTypeAny(type); } } } - else { - if (getESModuleInterop(compilerOptions) && - moduleKind !== ModuleKind.System && - (moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && - idText(node.propertyName || node.name) === "default") { - checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); - } - } } - function checkExportAssignment(node: ExportAssignment) { - const illegalContextMessage = node.isExportEquals - ? Diagnostics.An_export_assignment_must_be_at_the_top_level_of_a_file_or_module_declaration - : Diagnostics.A_default_export_must_be_at_the_top_level_of_a_file_or_module_declaration; - if (checkGrammarModuleElementContext(node, illegalContextMessage)) { - // If we hit an export assignment in an illegal context, just bail out to avoid cascading errors. - return; + function getSymbolOfNameOrPropertyAccessExpression(name: EntityName | PrivateIdentifier | PropertyAccessExpression | JSDocMemberName): Symbol | undefined { + if (isDeclarationName(name)) { + return getSymbolOfNode(name.parent); } - const container = node.parent.kind === SyntaxKind.SourceFile ? node.parent : node.parent.parent as ModuleDeclaration; - 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); + 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) && !isJSDocMemberName(name) && !isThisPropertyAndThisTyped(name.parent as PropertyAccessExpression)) { + const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(name); + if (specialPropertyAssignmentSymbol) { + return specialPropertyAssignmentSymbol; + } } + } - return; + 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; + } } - // Grammar checking - if (!checkGrammarModifiers(node) && hasEffectiveModifiers(node)) { - grammarErrorOnFirstToken(node, Diagnostics.An_export_assignment_cannot_have_modifiers); + else if (isEntityName(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); } - const typeAnnotationNode = getEffectiveTypeAnnotationNode(node); - if (typeAnnotationNode) { - checkTypeAssignableTo(checkExpressionCached(node.expression), getTypeFromTypeNode(typeAnnotationNode), node.expression); + if (isEntityName(name)) { + const possibleImportNode = isImportTypeQualifierPart(name); + if (possibleImportNode) { + getTypeFromTypeNode(possibleImportNode); + const sym = getNodeLinks(name).resolvedSymbol; + return sym === unknownSymbol ? undefined : sym; + } } - const isIllegalExportDefaultInCJS = !node.isExportEquals && - !(node.flags & NodeFlags.Ambient) && - compilerOptions.verbatimModuleSyntax && - (moduleKind === ModuleKind.CommonJS || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS); + while (isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName(name)) { + name = name.parent as QualifiedName | PropertyAccessEntityNameExpression | JSDocMemberName; + } - if (node.expression.kind === SyntaxKind.Identifier) { - const id = node.expression as Identifier; - const sym = getExportSymbolOfValueSymbolIfExported(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 }`) - if (getAllSymbolFlags(sym) & SymbolFlags.Value) { - // However if it is a value, we need to check it's being used correctly - checkExpressionCached(id); - if (!isIllegalExportDefaultInCJS && !(node.flags & NodeFlags.Ambient) && compilerOptions.verbatimModuleSyntax && getTypeOnlyAliasDeclaration(sym, SymbolFlags.Value)) { - error(id, - node.isExportEquals - ? Diagnostics.An_export_declaration_must_reference_a_real_value_when_verbatimModuleSyntax_is_enabled_but_0_resolves_to_a_type_only_declaration - : Diagnostics.An_export_default_must_reference_a_real_value_when_verbatimModuleSyntax_is_enabled_but_0_resolves_to_a_type_only_declaration, - idText(id)); - } - } - else if (!isIllegalExportDefaultInCJS && !(node.flags & NodeFlags.Ambient) && compilerOptions.verbatimModuleSyntax) { - error(id, - node.isExportEquals - ? Diagnostics.An_export_declaration_must_reference_a_value_when_verbatimModuleSyntax_is_enabled_but_0_only_refers_to_a_type - : Diagnostics.An_export_default_must_reference_a_value_when_verbatimModuleSyntax_is_enabled_but_0_only_refers_to_a_type, - idText(id)); + if (isInNameOfExpressionWithTypeArguments(name)) { + let meaning = SymbolFlags.None; + if (name.parent.kind === SyntaxKind.ExpressionWithTypeArguments) { + // An 'ExpressionWithTypeArguments' may appear in type space (interface Foo extends Bar), + // value space (return foo), or both(class Foo extends Bar); ensure the meaning matches. + meaning = isPartOfTypeNode(name) ? SymbolFlags.Type : SymbolFlags.Value; + + // In a class 'extends' clause we are also looking for a value. + if (isExpressionWithTypeArgumentsInClassExtendsClause(name.parent)) { + meaning |= SymbolFlags.Value; } } else { - checkExpressionCached(id); // doesn't resolve, check as expression to mark as error + meaning = SymbolFlags.Namespace; } - if (getEmitDeclarations(compilerOptions)) { - collectLinkedAliases(id, /*setVisibility*/ true); + meaning |= SymbolFlags.Alias; + const entityNameSymbol = isEntityNameExpression(name) ? resolveEntityName(name, meaning, /*ignoreErrors*/ true) : undefined; + if (entityNameSymbol) { + return entityNameSymbol; } } - else { - checkExpressionCached(node.expression); - } - if (isIllegalExportDefaultInCJS) { - error(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); + if (name.parent.kind === SyntaxKind.JSDocParameterTag) { + return getParameterSymbolFromJSDoc(name.parent as JSDocParameterTag); } - 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 (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 (node.isExportEquals) { - // Forbid export= in esm implementation files, and esm mode declaration files - if (moduleKind >= ModuleKind.ES2015 && - ((node.flags & NodeFlags.Ambient && getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.ESNext) || - (!(node.flags & NodeFlags.Ambient) && getSourceFileOfNode(node).impliedNodeFormat !== ModuleKind.CommonJS))) { - // 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 && !(node.flags & NodeFlags.Ambient)) { - // system modules does not support export assignment - grammarErrorOnNode(node, Diagnostics.Export_assignment_is_not_supported_when_module_flag_is_system); + if (isExpressionNode(name)) { + if (nodeIsMissing(name)) { + // Missing entity name. + return undefined; } - } - } - - function hasExportedMembers(moduleSymbol: Symbol) { - return forEachEntry(moduleSymbol.exports!, (_, id) => id !== "export="); - } - function checkExternalModuleExports(node: SourceFile | ModuleDeclaration) { - const moduleSymbol = getSymbolOfDeclaration(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 (declaration && !isTopLevelInExternalModuleAugmentation(declaration) && !isInJSFile(declaration)) { - error(declaration, Diagnostics.An_export_assignment_cannot_be_used_in_a_module_with_other_exported_elements); + const isJSDoc = findAncestor(name, or(isJSDocLinkLike, isJSDocNameReference, isJSDocMemberName)); + const meaning = isJSDoc ? SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value : SymbolFlags.Value; + if (name.kind === SyntaxKind.Identifier) { + if (isJSXTagName(name) && isJsxIntrinsicTagName(name)) { + const symbol = getIntrinsicTagSymbol(name.parent as JsxOpeningLikeElement); + return symbol === unknownSymbol ? undefined : symbol; } - } - // 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.Enum)) { - return; + const result = resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name)); + if (!result && isJSDoc) { + const container = findAncestor(name, or(isClassLike, isInterfaceDeclaration)); + if (container) { + return resolveJSDocMemberName(name, /*ignoreErrors*/ false, getSymbolOfDeclaration(container)); } - const exportedDeclarationsCount = countWhere(declarations, and(isNotOverloadAndNotAccessor, not(isInterfaceDeclaration))); - 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 (result && isJSDoc) { + const container = getJSDocHost(name); + if (container && isEnumMember(container) && container === result.valueDeclaration) { + return resolveEntityName(name, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, getSourceFileOfNode(container)) || result; } - if (exportedDeclarationsCount > 1) { - if (!isDuplicatedCommonJSExport(declarations)) { - for (const declaration of declarations!) { - if (isNotOverload(declaration)) { - diagnostics.add(createDiagnosticForNode(declaration, Diagnostics.Cannot_redeclare_exported_variable_0, unescapeLeadingUnderscores(id))); + } + return result; + } + else if (isPrivateIdentifier(name)) { + return getSymbolForPrivateIdentifierExpression(name); + } + 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, CheckMode.Normal); + if (!links.resolvedSymbol) { + const expressionType = checkExpressionCached(name.expression); + const infos = getApplicableIndexInfos(expressionType, getLiteralTypeFromPropertyName(name.name)); + if (infos.length && (expressionType as ObjectType).members) { + const resolved = resolveStructuredTypeMembers(expressionType as ObjectType); + const symbol = resolved.members.get(InternalSymbolName.Index); + if (infos === getIndexInfosOfType(expressionType)) { + links.resolvedSymbol = symbol; + } + else if (symbol) { + const symbolLinks = getSymbolLinks(symbol); + const declarationList = mapDefined(infos, i => i.declaration); + const nodeListId = map(declarationList, getNodeId).join(","); + if (!symbolLinks.filteredIndexSymbolCache) { + symbolLinks.filteredIndexSymbolCache = new Map(); + } + if (symbolLinks.filteredIndexSymbolCache.has(nodeListId)) { + links.resolvedSymbol = symbolLinks.filteredIndexSymbolCache.get(nodeListId)!; + } + else { + const copy = createSymbol(SymbolFlags.Signature, InternalSymbolName.Index); + copy.declarations = mapDefined(infos, i => i.declaration); + copy.parent = expressionType.aliasSymbol ? expressionType.aliasSymbol : expressionType.symbol ? expressionType.symbol : getSymbolAtLocation(copy.declarations[0].parent); + symbolLinks.filteredIndexSymbolCache.set(nodeListId, copy); + links.resolvedSymbol = symbolLinks.filteredIndexSymbolCache.get(nodeListId)!; } } } } - }); + } + else { + checkQualifiedName(name, CheckMode.Normal); + } + if (!links.resolvedSymbol && isJSDoc && isQualifiedName(name)) { + return resolveJSDocMemberName(name); + } + return links.resolvedSymbol; + } + else if (isJSDocMemberName(name)) { + return resolveJSDocMemberName(name); } - links.exportsChecked = true; } - } + else if (isTypeReferenceIdentifier(name as EntityName)) { + const meaning = name.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace; + const symbol = resolveEntityName(name as EntityName, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true); + return symbol && symbol !== unknownSymbol ? symbol : getUnresolvedSymbolForEntityName(name as EntityName); + } + if (name.parent.kind === SyntaxKind.TypePredicate) { + return resolveEntityName(name as Identifier, /*meaning*/ SymbolFlags.FunctionScopedVariable); + } - function isDuplicatedCommonJSExport(declarations: Declaration[] | undefined) { - return declarations - && declarations.length > 1 - && declarations.every(d => isInJSFile(d) && isAccessExpression(d) && (isExportsIdentifier(d.expression) || isModuleExportsAccessExpression(d.expression))); + return undefined; } - function checkSourceElement(node: Node | undefined): void { - if (node) { - const saveCurrentNode = currentNode; - currentNode = node; - instantiationCount = 0; - checkSourceElementWorker(node); - currentNode = saveCurrentNode; + /** + * Recursively resolve entity names and jsdoc instance references: + * 1. K#m as K.prototype.m for a class (or other value) K + * 2. K.m as K.prototype.m + * 3. I.m as I.m for a type I, or any other I.m that fails to resolve in (1) or (2) + * + * For unqualified names, a container K may be provided as a second argument. + */ + function resolveJSDocMemberName(name: EntityName | JSDocMemberName, ignoreErrors?: boolean, container?: Symbol): Symbol | undefined { + if (isEntityName(name)) { + // resolve static values first + const meaning = SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value; + let symbol = resolveEntityName(name, meaning, ignoreErrors, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name)); + if (!symbol && isIdentifier(name) && container) { + symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(container), name.escapedText, meaning)); + } + if (symbol) { + return symbol; + } + } + const left = isIdentifier(name) ? container : resolveJSDocMemberName(name.left, ignoreErrors, container); + const right = isIdentifier(name) ? name.escapedText : name.right.escapedText; + if (left) { + const proto = left.flags & SymbolFlags.Value && getPropertyOfType(getTypeOfSymbol(left), "prototype" as __String); + const t = proto ? getTypeOfSymbol(proto) : getDeclaredTypeOfSymbol(left); + return getPropertyOfType(t, right); } } - function checkSourceElementWorker(node: Node): void { - if (canHaveJSDoc(node)) { - forEach(node.jsDoc, ({ comment, tags }) => { - checkJSDocCommentWorker(comment); - forEach(tags, tag => { - checkJSDocCommentWorker(tag.comment); - if (isInJSFile(node)) { - checkSourceElement(tag); - } - }); - }); + function getSymbolAtLocation(node: Node, ignoreErrors?: boolean): Symbol | undefined { + if (isSourceFile(node)) { + return isExternalModule(node) ? getMergedSymbol(node.symbol) : undefined; } + const { parent } = node; + const grandParent = parent.parent; - 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 (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return undefined; } - if (kind >= SyntaxKind.FirstStatement && kind <= SyntaxKind.LastStatement && canHaveFlowNode(node) && node.flowNode && !isReachableFlowNode(node.flowNode)) { - errorOrSuggestion(compilerOptions.allowUnreachableCode === false, node, Diagnostics.Unreachable_code_detected); + + if (isDeclarationNameOrImportPropertyName(node)) { + // This is a declaration, call getSymbolOfNode + const parentSymbol = getSymbolOfDeclaration(parent as Declaration)!; + return isImportOrExportSpecifier(node.parent) && node.parent.propertyName === node + ? getImmediateAliasedSymbol(parentSymbol) + : parentSymbol; + } + else if (isLiteralComputedPropertyDeclarationName(node)) { + return getSymbolOfDeclaration(parent.parent as Declaration); } - switch (kind) { - case SyntaxKind.TypeParameter: - return checkTypeParameter(node as TypeParameterDeclaration); - case SyntaxKind.Parameter: - return checkParameter(node as ParameterDeclaration); - case SyntaxKind.PropertyDeclaration: - return checkPropertyDeclaration(node as PropertyDeclaration); - case SyntaxKind.PropertySignature: - return checkPropertySignature(node as PropertySignature); - case SyntaxKind.ConstructorType: - case SyntaxKind.FunctionType: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - return checkSignatureDeclaration(node as SignatureDeclaration); - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - return checkMethodDeclaration(node as MethodDeclaration | MethodSignature); - case SyntaxKind.ClassStaticBlockDeclaration: - return checkClassStaticBlockDeclaration(node as ClassStaticBlockDeclaration); - case SyntaxKind.Constructor: - return checkConstructorDeclaration(node as ConstructorDeclaration); - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return checkAccessorDeclaration(node as AccessorDeclaration); - case SyntaxKind.TypeReference: - return checkTypeReferenceNode(node as TypeReferenceNode); - case SyntaxKind.TypePredicate: - return checkTypePredicate(node as TypePredicateNode); - case SyntaxKind.TypeQuery: - return checkTypeQuery(node as TypeQueryNode); - case SyntaxKind.TypeLiteral: - return checkTypeLiteral(node as TypeLiteralNode); - case SyntaxKind.ArrayType: - return checkArrayType(node as ArrayTypeNode); - case SyntaxKind.TupleType: - return checkTupleType(node as TupleTypeNode); - case SyntaxKind.UnionType: - case SyntaxKind.IntersectionType: - return checkUnionOrIntersectionType(node as UnionOrIntersectionTypeNode); - case SyntaxKind.ParenthesizedType: - case SyntaxKind.OptionalType: - case SyntaxKind.RestType: - return checkSourceElement((node as ParenthesizedTypeNode | OptionalTypeNode | RestTypeNode).type); + if (node.kind === SyntaxKind.Identifier) { + if (isInRightSideOfImportOrExportAssignment(node as Identifier)) { + return getSymbolOfNameOrPropertyAccessExpression(node as Identifier); + } + else if (parent.kind === SyntaxKind.BindingElement && + grandParent.kind === SyntaxKind.ObjectBindingPattern && + node === (parent as BindingElement).propertyName) { + const typeOfPattern = getTypeOfNode(grandParent); + const propertyDeclaration = getPropertyOfType(typeOfPattern, (node as Identifier).escapedText); + + if (propertyDeclaration) { + return propertyDeclaration; + } + } + else if (isMetaProperty(parent) && parent.name === node) { + if (parent.keywordToken === SyntaxKind.NewKeyword && idText(node as Identifier) === "target") { + // `target` in `new.target` + return checkNewTargetMetaProperty(parent).symbol; + } + // The `meta` in `import.meta` could be given `getTypeOfNode(parent).symbol` (the `ImportMeta` interface symbol), but + // we have a fake expression type made for other reasons already, whose transient `meta` + // member should more exactly be the kind of (declarationless) symbol we want. + // (See #44364 and #45031 for relevant implementation PRs) + if (parent.keywordToken === SyntaxKind.ImportKeyword && idText(node as Identifier) === "meta") { + return getGlobalImportMetaExpressionType().members!.get("meta" as __String); + } + // no other meta properties are valid syntax, thus no others should have symbols + return undefined; + } + } + + switch (node.kind) { + case SyntaxKind.Identifier: + case SyntaxKind.PrivateIdentifier: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.QualifiedName: + if (!isThisInTypeQuery(node)) { + return getSymbolOfNameOrPropertyAccessExpression(node as EntityName | PrivateIdentifier | PropertyAccessExpression); + } + // falls through + + case SyntaxKind.ThisKeyword: + const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ 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 checkThisType(node as ThisTypeNode); - case SyntaxKind.TypeOperator: - return checkTypeOperator(node as TypeOperatorNode); - case SyntaxKind.ConditionalType: - return checkConditionalType(node as ConditionalTypeNode); - case SyntaxKind.InferType: - return checkInferType(node as InferTypeNode); - case SyntaxKind.TemplateLiteralType: - return checkTemplateLiteralType(node as TemplateLiteralTypeNode); - case SyntaxKind.ImportType: - return checkImportType(node as ImportTypeNode); - case SyntaxKind.NamedTupleMember: - return checkNamedTupleMember(node as NamedTupleMember); - 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.JSDocLink: - case SyntaxKind.JSDocLinkCode: - case SyntaxKind.JSDocLinkPlain: - return checkJSDocLinkLikeTag(node as JSDocLink | JSDocLinkCode | JSDocLinkPlain); - case SyntaxKind.JSDocParameterTag: - return checkJSDocParameterTag(node as JSDocParameterTag); - case SyntaxKind.JSDocPropertyTag: - return checkJSDocPropertyTag(node as JSDocPropertyTag); - case SyntaxKind.JSDocFunctionType: - checkJSDocFunctionType(node as JSDocFunctionType); + 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 as ClassDeclaration).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 as ImportDeclaration).moduleSpecifier === node) || + ((isInJSFile(node) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Bundler && isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ false)) || isImportCall(node.parent)) || + (isLiteralTypeNode(node.parent) && isLiteralImportTypeNode(node.parent.parent) && node.parent.parent.argument === node.parent) + ) { + return resolveExternalModuleName(node, node as LiteralExpression, ignoreErrors); + } + if (isCallExpression(parent) && isBindableObjectDefinePropertyCall(parent) && parent.arguments[1] === node) { + return getSymbolOfDeclaration(parent); + } // 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.JSDocPublicTag: - case SyntaxKind.JSDocProtectedTag: - case SyntaxKind.JSDocPrivateTag: - return checkJSDocAccessibilityModifiers(node as JSDocPublicTag | JSDocProtectedTag | JSDocPrivateTag); - case SyntaxKind.JSDocSatisfiesTag: - return checkJSDocSatisfiesTag(node as JSDocSatisfiesTag); - case SyntaxKind.IndexedAccessType: - return checkIndexedAccessType(node as IndexedAccessTypeNode); - case SyntaxKind.MappedType: - return checkMappedType(node as MappedTypeNode); - case SyntaxKind.FunctionDeclaration: - return checkFunctionDeclaration(node as FunctionDeclaration); - case SyntaxKind.Block: - case SyntaxKind.ModuleBlock: - return checkBlock(node as Block); - case SyntaxKind.VariableStatement: - return checkVariableStatement(node as VariableStatement); - case SyntaxKind.ExpressionStatement: - return checkExpressionStatement(node as ExpressionStatement); - case SyntaxKind.IfStatement: - return checkIfStatement(node as IfStatement); - case SyntaxKind.DoStatement: - return checkDoStatement(node as DoStatement); - case SyntaxKind.WhileStatement: - return checkWhileStatement(node as WhileStatement); - case SyntaxKind.ForStatement: - return checkForStatement(node as ForStatement); - case SyntaxKind.ForInStatement: - return checkForInStatement(node as ForInStatement); - case SyntaxKind.ForOfStatement: - return checkForOfStatement(node as ForOfStatement); - case SyntaxKind.ContinueStatement: - case SyntaxKind.BreakStatement: - return checkBreakOrContinueStatement(node as BreakOrContinueStatement); - case SyntaxKind.ReturnStatement: - return checkReturnStatement(node as ReturnStatement); - case SyntaxKind.WithStatement: - return checkWithStatement(node as WithStatement); - case SyntaxKind.SwitchStatement: - return checkSwitchStatement(node as SwitchStatement); - case SyntaxKind.LabeledStatement: - return checkLabeledStatement(node as LabeledStatement); - case SyntaxKind.ThrowStatement: - return checkThrowStatement(node as ThrowStatement); - case SyntaxKind.TryStatement: - return checkTryStatement(node as TryStatement); - case SyntaxKind.VariableDeclaration: - return checkVariableDeclaration(node as VariableDeclaration); - case SyntaxKind.BindingElement: - return checkBindingElement(node as BindingElement); - case SyntaxKind.ClassDeclaration: - return checkClassDeclaration(node as ClassDeclaration); - case SyntaxKind.InterfaceDeclaration: - return checkInterfaceDeclaration(node as InterfaceDeclaration); - case SyntaxKind.TypeAliasDeclaration: - return checkTypeAliasDeclaration(node as TypeAliasDeclaration); - case SyntaxKind.EnumDeclaration: - return checkEnumDeclaration(node as EnumDeclaration); - case SyntaxKind.ModuleDeclaration: - return checkModuleDeclaration(node as ModuleDeclaration); - case SyntaxKind.ImportDeclaration: - return checkImportDeclaration(node as ImportDeclaration); - case SyntaxKind.ImportEqualsDeclaration: - return checkImportEqualsDeclaration(node as ImportEqualsDeclaration); - case SyntaxKind.ExportDeclaration: - return checkExportDeclaration(node as ExportDeclaration); - case SyntaxKind.ExportAssignment: - return checkExportAssignment(node as ExportAssignment); - case SyntaxKind.EmptyStatement: - case SyntaxKind.DebuggerStatement: - checkGrammarStatementInAmbientContext(node); - return; - case SyntaxKind.MissingDeclaration: - return checkMissingDeclaration(node); + + 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; + + case SyntaxKind.ImportKeyword: + case SyntaxKind.NewKeyword: + return isMetaProperty(node.parent) ? checkMetaPropertyKeyword(node.parent).symbol : undefined; + case SyntaxKind.MetaProperty: + return checkExpression(node as Expression).symbol; + case SyntaxKind.JsxNamespacedName: + if (isJSXTagName(node) && isJsxIntrinsicTagName(node)) { + const symbol = getIntrinsicTagSymbol(node.parent as JsxOpeningLikeElement); + return symbol === unknownSymbol ? undefined : symbol; + } + // falls through + + default: + return undefined; } } - function checkJSDocCommentWorker(node: string | readonly JSDocComment[] | undefined) { - if (isArray(node)) { - forEach(node, tag => { - if (isJSDocLinkLike(tag)) { - checkSourceElement(tag); - } - }); + function getIndexInfosAtLocation(node: Node): readonly IndexInfo[] | undefined { + if (isIdentifier(node) && isPropertyAccessExpression(node.parent) && node.parent.name === node) { + const keyType = getLiteralTypeFromPropertyName(node); + const objectType = getTypeOfExpression(node.parent.expression); + const objectTypes = objectType.flags & TypeFlags.Union ? (objectType as UnionType).types : [objectType]; + return flatMap(objectTypes, t => filter(getIndexInfosOfType(t), info => isApplicableIndexType(keyType, info.keyType))); } + return undefined; } - function checkJSDocTypeIsInJsFile(node: Node): void { - if (!isInJSFile(node)) { - if (isJSDocNonNullableType(node) || isJSDocNullableType(node)) { - const token = tokenToString(isJSDocNonNullableType(node) ? SyntaxKind.ExclamationToken : SyntaxKind.QuestionToken); - const diagnostic = node.postfix - ? Diagnostics._0_at_the_end_of_a_type_is_not_valid_TypeScript_syntax_Did_you_mean_to_write_1 - : Diagnostics._0_at_the_start_of_a_type_is_not_valid_TypeScript_syntax_Did_you_mean_to_write_1; - const typeNode = node.type; - const type = getTypeFromTypeNode(typeNode); - grammarErrorOnNode(node, diagnostic, token, typeToString( - isJSDocNullableType(node) && !(type === neverType || type === voidType) - ? getUnionType(append([type, undefinedType], node.postfix ? undefined : nullType)) : type)); - } - else { - grammarErrorOnNode(node, Diagnostics.JSDoc_types_can_only_be_used_inside_documentation_comments); - } + function getShorthandAssignmentValueSymbol(location: Node | undefined): Symbol | undefined { + if (location && location.kind === SyntaxKind.ShorthandPropertyAssignment) { + return resolveEntityName((location as ShorthandPropertyAssignment).name, SymbolFlags.Value | SymbolFlags.Alias); } + return undefined; } - function checkJSDocVariadicType(node: JSDocVariadicType): void { - checkJSDocTypeIsInJsFile(node); - checkSourceElement(node.type); + /** 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); + } + } - // 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; + function getTypeOfNode(node: Node): Type { + if (isSourceFile(node) && !isExternalModule(node)) { + return errorType; } - if (!isJSDocTypeExpression(parent)) { - error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); + if (node.flags & NodeFlags.InWithStatement) { + // We cannot answer semantic questions within a with block, do not proceed any further + return errorType; } - const paramTag = node.parent.parent; - if (!isJSDocParameterTag(paramTag)) { - error(node, Diagnostics.JSDoc_may_only_appear_in_the_last_parameter_of_a_signature); - return; + const classDecl = tryGetClassImplementingOrExtendingExpressionWithTypeArguments(node); + const classType = classDecl && getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(classDecl.class)); + if (isPartOfTypeNode(node)) { + const typeFromTypeNode = getTypeFromTypeNode(node as TypeNode); + return classType ? getTypeWithThisArgument(typeFromTypeNode, classType.thisType) : typeFromTypeNode; } - const param = getParameterSymbolFromJSDoc(paramTag); - if (!param) { - // We will error in `checkJSDocParameterTag`. - return; + if (isExpressionNode(node)) { + return getRegularTypeOfExpression(node as Expression); } - const host = getHostSignatureFromJSDoc(paramTag); - if (!host || last(host.parameters).symbol !== param) { - error(node, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); + 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; } - } - 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); - const isCallbackTag = isJSDocCallbackTag(paramTag.parent.parent); - if (host || isCallbackTag) { - /* - 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 = isCallbackTag - ? lastOrUndefined((paramTag.parent.parent as unknown as JSDocCallbackTag).typeExpression.parameters) - : lastOrUndefined(host!.parameters); - const symbol = getParameterSymbolFromJSDoc(paramTag); - if (!lastParamDeclaration || - symbol && lastParamDeclaration.symbol === symbol && isRestParameter(lastParamDeclaration)) { - return createArrayType(type); - } + if (isTypeDeclaration(node)) { + // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration + const symbol = getSymbolOfDeclaration(node); + return getDeclaredTypeOfSymbol(symbol); + } + + if (isTypeDeclarationName(node)) { + const symbol = getSymbolAtLocation(node); + return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; + } + + if (isBindingElement(node)) { + return getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ true, CheckMode.Normal) || errorType; + } + + if (isDeclaration(node)) { + // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration + const symbol = getSymbolOfDeclaration(node); + return symbol ? getTypeOfSymbol(symbol) : errorType; + } + + if (isDeclarationNameOrImportPropertyName(node)) { + const symbol = getSymbolAtLocation(node); + if (symbol) { + return getTypeOfSymbol(symbol); } + return errorType; } - if (isParameter(parent) && isJSDocFunctionType(parent.parent)) { - return createArrayType(type); + + if (isBindingPattern(node)) { + return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true, CheckMode.Normal) || errorType; } - return addOptionality(type); + + if (isInRightSideOfImportOrExportAssignment(node as Identifier)) { + const symbol = getSymbolAtLocation(node); + if (symbol) { + const declaredType = getDeclaredTypeOfSymbol(symbol); + return !isErrorType(declaredType) ? declaredType : getTypeOfSymbol(symbol); + } + } + + if (isMetaProperty(node.parent) && node.parent.keywordToken === node.kind) { + return checkMetaPropertyKeyword(node.parent); + } + + return errorType; } - // 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"; + // Gets the type of object literal or array literal of destructuring assignment. + // { a } from + // for ( { a } of elems) { // } - // 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 ||= new Set(); - links.deferredNodes.add(node); + // [ 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 as ForOfStatement); + return checkDestructuringAssignment(expr, iteratedType || errorType); } - else { - Debug.assert(!links.deferredNodes, "A type-checked file should have no deferred nodes."); + // If this is from "for" initializer + // for ({a } = elems[0];.....) { } + if (expr.parent.kind === SyntaxKind.BinaryExpression) { + const iteratedType = getTypeOfExpression((expr.parent as BinaryExpression).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); } - function checkDeferredNodes(context: SourceFile) { - const links = getNodeLinks(context); - if (links.deferredNodes) { - links.deferredNodes.forEach(checkDeferredNode); + // 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 as Expression; } - links.deferredNodes = undefined; + return getRegularTypeOfLiteralType(getTypeOfExpression(expr)); } - function checkDeferredNode(node: Node) { - tracing?.push(tracing.Phase.Check, "checkDeferredNode", { kind: node.kind, pos: node.pos, end: node.end, path: (node as TracingNode).tracingPath }); - 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 as FunctionExpression); - break; - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - checkAccessorDeclaration(node as AccessorDeclaration); - break; - case SyntaxKind.ClassExpression: - checkClassExpressionDeferred(node as ClassExpression); - break; - case SyntaxKind.TypeParameter: - checkTypeParameterDeferred(node as TypeParameterDeclaration); - break; - case SyntaxKind.JsxSelfClosingElement: - checkJsxSelfClosingElementDeferred(node as JsxSelfClosingElement); - break; - case SyntaxKind.JsxElement: - checkJsxElementDeferred(node as JsxElement); - break; - case SyntaxKind.TypeAssertionExpression: - case SyntaxKind.AsExpression: - case SyntaxKind.ParenthesizedExpression: - checkAssertionDeferred(node as AssertionExpression | JSDocTypeAssertion); + /** + * 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 isStatic(node) + ? getTypeOfSymbol(classSymbol) + : getDeclaredTypeOfSymbol(classSymbol); + } + + function getClassElementPropertyKeyType(element: ClassElement) { + const name = element.name!; + switch (name.kind) { + case SyntaxKind.Identifier: + return getStringLiteralType(idText(name)); + case SyntaxKind.NumericLiteral: + case SyntaxKind.StringLiteral: + return getStringLiteralType(name.text); + case SyntaxKind.ComputedPropertyName: + const nameType = checkComputedPropertyName(name); + return isTypeAssignableToKind(nameType, TypeFlags.ESSymbolLike) ? nameType : stringType; + default: + return Debug.fail("Unsupported property name."); } - currentNode = saveCurrentNode; - tracing?.pop(); } - function checkSourceFile(node: SourceFile) { - tracing?.push(tracing.Phase.Check, "checkSourceFile", { path: node.path }, /*separateBeginAndEnd*/ true); - performance.mark("beforeCheck"); - checkSourceFileWorker(node); - performance.mark("afterCheck"); - performance.measure("Check", "beforeCheck", "afterCheck"); - tracing?.pop(); + // 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 unusedIsError(kind: UnusedKind, isAmbient: boolean): boolean { - if (isAmbient) { - return false; + function typeHasCallOrConstructSignatures(type: Type): boolean { + return getSignaturesOfType(type, SignatureKind.Call).length !== 0 || getSignaturesOfType(type, SignatureKind.Construct).length !== 0; + } + + 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)); } - switch (kind) { - case UnusedKind.Local: - return !!compilerOptions.noUnusedLocals; - case UnusedKind.Parameter: - return !!compilerOptions.noUnusedParameters; - default: - return Debug.assertNever(kind); + else if (symbol.flags & SymbolFlags.Transient) { + const { links: { leftSpread, rightSpread, syntheticOrigin } } = symbol as TransientSymbol; + return leftSpread ? [leftSpread, rightSpread!] + : syntheticOrigin ? [syntheticOrigin] + : singleElementArray(tryGetTarget(symbol)); + } + return undefined; + } + function tryGetTarget(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 && !!(getAllSymbolFlags(s) & 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 as SourceFile; + 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) && getSymbolOfDeclaration(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 { + const specifier = getIdentifierGeneratedImportReference(nodeIn); + if (specifier) { + return specifier; + } + const node = getParseTreeNode(nodeIn, isIdentifier); + if (node) { + const symbol = getReferencedValueOrAliasSymbol(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, SymbolFlags.Value)) { + return getDeclarationOfAliasSymbol(symbol); + } + } + + return undefined; + } + + function isSymbolOfDestructuredElementOfCatchBinding(symbol: Symbol) { + return symbol.valueDeclaration + && isBindingElement(symbol.valueDeclaration) + && walkUpBindingElementsAndPatterns(symbol.valueDeclaration).parent.kind === SyntaxKind.CatchClause; + } + + function isSymbolOfDeclarationWithCollidingName(symbol: Symbol): boolean { + if (symbol.flags & SymbolFlags.BlockScoped && symbol.valueDeclaration && !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; } - function getPotentiallyUnusedIdentifiers(sourceFile: SourceFile): readonly PotentiallyUnusedIdentifier[] { - return allPotentiallyUnusedIdentifiers.get(sourceFile.path) || emptyArray; + // 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; } - // 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; + // 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 = getSymbolOfDeclaration(node); + if (symbol) { + return isSymbolOfDeclarationWithCollidingName(symbol); } + } - // Grammar checking - checkGrammarSourceFile(node); - - clear(potentialThisCollisions); - clear(potentialNewTargetCollisions); - clear(potentialWeakMapSetCollisions); - clear(potentialReflectCollisions); - clear(potentialUnusedRenamedBindingElementsInTypes); + return false; + } - forEach(node.statements, checkSourceElement); - checkSourceElement(node.endOfFileToken); + function isValueAliasDeclaration(node: Node): boolean { + Debug.assert(canCollectSymbolAliasAccessabilityData); + switch (node.kind) { + case SyntaxKind.ImportEqualsDeclaration: + return isAliasResolvedToValue(getSymbolOfDeclaration(node as ImportEqualsDeclaration)); + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + const symbol = getSymbolOfDeclaration(node as ImportClause | NamespaceImport | ImportSpecifier | ExportSpecifier); + return !!symbol && isAliasResolvedToValue(symbol) && !getTypeOnlyAliasDeclaration(symbol, SymbolFlags.Value); + case SyntaxKind.ExportDeclaration: + const exportClause = (node as ExportDeclaration).exportClause; + return !!exportClause && ( + isNamespaceExport(exportClause) || + some(exportClause.elements, isValueAliasDeclaration) + ); + case SyntaxKind.ExportAssignment: + return (node as ExportAssignment).expression && (node as ExportAssignment).expression.kind === SyntaxKind.Identifier ? + isAliasResolvedToValue(getSymbolOfDeclaration(node as ExportAssignment)) : + true; + } + return false; + } - checkDeferredNodes(node); + 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; + } - if (isExternalOrCommonJsModule(node)) { - registerForUnusedIdentifiersCheck(node); - } + const isValue = isAliasResolvedToValue(getSymbolOfDeclaration(node)); + return isValue && node.moduleReference && !nodeIsMissing(node.moduleReference); + } - addLazyDiagnostic(() => { - // This relies on the results of other lazy diagnostics, so must be computed after them - 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 (!node.isDeclarationFile) { - checkPotentialUncheckedRenamedBindingElementsInTypes(); - } - }); + function isAliasResolvedToValue(symbol: Symbol | undefined): boolean { + if (!symbol) { + return false; + } + const target = getExportSymbolOfValueSymbolIfExported(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 !!((getAllSymbolFlags(target) ?? -1) & SymbolFlags.Value) && + (shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target)); + } - if (compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && - !node.isDeclarationFile && - isExternalModule(node) - ) { - checkImportsForTypeOnlyConversion(node); - } + function isConstEnumOrConstEnumOnlyModule(s: Symbol): boolean { + return isConstEnumSymbol(s) || !!s.constEnumOnlyModule; + } - if (isExternalOrCommonJsModule(node)) { - checkExternalModuleExports(node); + function isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean { + Debug.assert(canCollectSymbolAliasAccessabilityData); + if (isAliasSymbolDeclaration(node)) { + const symbol = getSymbolOfDeclaration(node as Declaration); + const links = symbol && getSymbolLinks(symbol); + if (links?.referenced) { + return true; } - - if (potentialThisCollisions.length) { - forEach(potentialThisCollisions, checkIfThisIsCapturedInEnclosingScope); - clear(potentialThisCollisions); + const target = getSymbolLinks(symbol).aliasTarget; + if (target && getEffectiveModifierFlags(node) & ModifierFlags.Export && + getAllSymbolFlags(target) & SymbolFlags.Value && + (shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target))) { + // An `export import ... =` of a value symbol is always considered referenced + return true; } + } - if (potentialNewTargetCollisions.length) { - forEach(potentialNewTargetCollisions, checkIfNewTargetIsCapturedInEnclosingScope); - clear(potentialNewTargetCollisions); - } + if (checkChildren) { + return !!forEachChild(node, node => isReferencedAliasDeclaration(node, checkChildren)); + } + return false; + } - if (potentialWeakMapSetCollisions.length) { - forEach(potentialWeakMapSetCollisions, checkWeakMapSetCollision); - clear(potentialWeakMapSetCollisions); - } + 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 = getSymbolOfDeclaration(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; + } - if (potentialReflectCollisions.length) { - forEach(potentialReflectCollisions, checkReflectCollision); - clear(potentialReflectCollisions); - } + function isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag): boolean { + return !!strictNullChecks && + !isOptionalParameter(parameter) && + !isJSDocParameterTag(parameter) && + !!parameter.initializer && + !hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); + } - links.flags |= NodeCheckFlags.TypeChecked; - } + function isOptionalUninitializedParameterProperty(parameter: ParameterDeclaration) { + return strictNullChecks && + isOptionalParameter(parameter) && + !parameter.initializer && + hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); } - 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); + function isExpandoFunctionDeclaration(node: Declaration): boolean { + const declaration = getParseTreeNode(node, isFunctionDeclaration); + if (!declaration) { + return false; } - finally { - cancellationToken = undefined; + const symbol = getSymbolOfDeclaration(declaration); + if (!symbol || !(symbol.flags & SymbolFlags.Function)) { + return false; } + return !!forEachEntry(getExportsOfSymbol(symbol), p => p.flags & SymbolFlags.Value && p.valueDeclaration && isPropertyAccessExpression(p.valueDeclaration)); } - function ensurePendingDiagnosticWorkComplete() { - // Invoke any existing lazy diagnostics to add them, clear the backlog of diagnostics - for (const cb of deferredDiagnosticsCallbacks) { - cb(); + function getPropertiesOfContainerFunction(node: Declaration): Symbol[] { + const declaration = getParseTreeNode(node, isFunctionDeclaration); + if (!declaration) { + return emptyArray; } - deferredDiagnosticsCallbacks = []; + const symbol = getSymbolOfDeclaration(declaration); + return symbol && getPropertiesOfType(getTypeOfSymbol(symbol)) || emptyArray; } - function checkSourceFileWithEagerDiagnostics(sourceFile: SourceFile) { - ensurePendingDiagnosticWorkComplete(); - // then setup diagnostics for immediate invocation (as we are about to collect them, and - // this avoids the overhead of longer-lived callbacks we don't need to allocate) - // This also serves to make the shift to possibly lazy diagnostics transparent to serial command-line scenarios - // (as in those cases, all the diagnostics will still be computed as the appropriate place in the tree, - // thus much more likely retaining the same union ordering as before we had lazy diagnostics) - const oldAddLazyDiagnostics = addLazyDiagnostic; - addLazyDiagnostic = cb => cb(); - checkSourceFile(sourceFile); - addLazyDiagnostic = oldAddLazyDiagnostics; + function getNodeCheckFlags(node: Node): NodeCheckFlags { + const nodeId = node.id || 0; + if (nodeId < 0 || nodeId >= nodeLinks.length) return 0; + return nodeLinks[nodeId]?.flags || 0; } - function getDiagnosticsWorker(sourceFile: SourceFile): Diagnostic[] { - if (sourceFile) { - ensurePendingDiagnosticWorkComplete(); - // 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; + function getEnumMemberValue(node: EnumMember): string | number | undefined { + computeEnumMemberValues(node.parent); + return getNodeLinks(node).enumMemberValue; + } - checkSourceFileWithEagerDiagnostics(sourceFile); + function canHaveConstantValue(node: Node): node is EnumMember | AccessExpression { + switch (node.kind) { + case SyntaxKind.EnumMember: + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return true; + } + return false; + } - 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); - } + function getConstantValue(node: EnumMember | AccessExpression): string | number | undefined { + if (node.kind === SyntaxKind.EnumMember) { + return getEnumMemberValue(node); + } - return semanticDiagnostics; + 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); + } } - // Global diagnostics are always added when a file is not provided to - // getDiagnostics - forEach(host.getSourceFiles(), checkSourceFileWithEagerDiagnostics); - return diagnostics.getDiagnostics(); + return undefined; } - function getGlobalDiagnostics(): Diagnostic[] { - ensurePendingDiagnosticWorkComplete(); - return diagnostics.getGlobalDiagnostics(); + function isFunctionType(type: Type): boolean { + return !!(type.flags & TypeFlags.Object) && getSignaturesOfType(type, SignatureKind.Call).length > 0; } - // Language service support + 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; - 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 []; + if (location) { + location = getParseTreeNode(location); + if (!location) return TypeReferenceSerializationKind.Unknown; } - const symbols = createSymbolTable(); - let isStaticSymbol = false; - - populateSymbols(); - - symbols.delete(InternalSymbolName.This); // Not a symbol, a keyword - return symbolsToArray(symbols); - - function populateSymbols() { - while (location) { - if (canHaveLocals(location) && location.locals && !isGlobalSourceFile(location)) { - copySymbols(location.locals, meaning); - } - - switch (location.kind) { - case SyntaxKind.SourceFile: - if (!isExternalModule(location as SourceFile)) break; - // falls through - case SyntaxKind.ModuleDeclaration: - copyLocallyVisibleExportSymbols(getSymbolOfDeclaration(location as ModuleDeclaration | SourceFile).exports!, meaning & SymbolFlags.ModuleMember); - break; - case SyntaxKind.EnumDeclaration: - copySymbols(getSymbolOfDeclaration(location as EnumDeclaration).exports!, meaning & SymbolFlags.EnumMember); - break; - case SyntaxKind.ClassExpression: - const className = (location as ClassExpression).name; - if (className) { - copySymbol((location as ClassExpression).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 (!isStaticSymbol) { - copySymbols(getMembersOfSymbol(getSymbolOfDeclaration(location as ClassDeclaration | InterfaceDeclaration)), meaning & SymbolFlags.Type); - } - break; - case SyntaxKind.FunctionExpression: - const funcName = (location as FunctionExpression).name; - if (funcName) { - copySymbol((location as FunctionExpression).symbol, meaning); - } - break; - } + // Resolve the symbol as a value to ensure the type can be reached at runtime during emit. + let isTypeOnly = false; + if (isQualifiedName(typeName)) { + const rootValueSymbol = resolveEntityName(getFirstIdentifier(typeName), SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location); + isTypeOnly = !!rootValueSymbol?.declarations?.every(isTypeOnlyImportOrExportDeclaration); + } + const valueSymbol = resolveEntityName(typeName, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location); + const resolvedSymbol = valueSymbol && valueSymbol.flags & SymbolFlags.Alias ? resolveAlias(valueSymbol) : valueSymbol; + isTypeOnly ||= !!(valueSymbol && getTypeOnlyAliasDeclaration(valueSymbol, SymbolFlags.Value)); - if (introducesArgumentsExoticObject(location)) { - copySymbol(argumentsSymbol, meaning); - } + // 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; + } - isStaticSymbol = isStatic(location); - location = location.parent; + const constructorType = getTypeOfSymbol(resolvedSymbol); + if (constructorType && isConstructorType(constructorType)) { + return isTypeOnly ? TypeReferenceSerializationKind.TypeWithCallSignature : TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue; } + } - copySymbols(globals, meaning); + // 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 (isErrorType(type)) { + 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; } + } - /** - * 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); - } - } - if (companionSymbolCache.has(symbol)) { - const id = symbol.escapedName; - if (!symbols.has(id)) { - symbols.set(id, symbol); - } - } - if (symbol.declarations && symbol.declarations[0] && isImportSpecifier(symbol.declarations[0])) { - const originalSymbol = getTargetOfImportSpecifier(symbol.declarations[0], false); - if (originalSymbol && companionSymbolCache.has(originalSymbol)) { - const id = symbol.escapedName; - if (!symbols.has(id)) { - symbols.set(id, symbol); - } - } - } + 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 = getSymbolOfDeclaration(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 copySymbols(source: SymbolTable, meaning: SymbolFlags): void { - if (meaning) { - source.forEach(symbol => { - copySymbol(symbol, meaning); - }); - } + 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 copyLocallyVisibleExportSymbols(source: SymbolTable, meaning: SymbolFlags): void { - if (meaning) { - source.forEach(symbol => { - // Similar condition as in `resolveNameHelper` - if (!getDeclarationOfKind(symbol, SyntaxKind.ExportSpecifier) && !getDeclarationOfKind(symbol, SyntaxKind.NamespaceExport)) { - copySymbol(symbol, meaning); - } - }); + 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); } } - } - function isTypeDeclarationName(name: Node): boolean { - return name.kind === SyntaxKind.Identifier && - isTypeDeclaration(name.parent) && - getNameOfDeclaration(name.parent) === name; + return resolveName(location, reference.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); } - // 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; + /** + * Get either a value-meaning symbol or an alias symbol. + * Unlike `getReferencedValueSymbol`, if the cached resolved symbol is the unknown symbol, + * we call `resolveName` to find a symbol. + * This is because when caching the resolved symbol, we only consider value symbols, but here + * we want to also get an alias symbol if one exists. + */ + function getReferencedValueOrAliasSymbol(reference: Identifier): Symbol | undefined { + const resolvedSymbol = getNodeLinks(reference).resolvedSymbol; + if (resolvedSymbol && resolvedSymbol !== unknownSymbol) { + return resolvedSymbol; } - return node.parent.kind === SyntaxKind.TypeReference; + return resolveName( + reference, + reference.escapedText, + SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, + /*nameNotFoundMessage*/ undefined, + /*nameArg*/ undefined, + /*isUse*/ true, + /*excludeGlobals*/ undefined, + /*getSpellingSuggestions*/ undefined); } - function isInNameOfExpressionWithTypeArguments(node: Node): boolean { - while (node.parent.kind === SyntaxKind.PropertyAccessExpression) { - node = node.parent; + 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 node.parent.kind === SyntaxKind.ExpressionWithTypeArguments; + return undefined; } - function forEachEnclosingClass(node: Node, callback: (node: ClassLikeDeclaration) => T | undefined): T | undefined { - let result: T | undefined; - let containingClass = getContainingClass(node); - while (containingClass) { - if (result = callback(containingClass)) break; - containingClass = getContainingClass(containingClass); + function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean { + if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) { + return isFreshLiteralType(getTypeOfSymbol(getSymbolOfDeclaration(node))); } - - return result; + return false; } - 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"; - } + function literalTypeToNode(type: FreshableType, enclosing: Node, tracker: SymbolTracker): Expression { + const enumResult = type.flags & TypeFlags.EnumLike ? 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); + } - return false; - }); + function createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, tracker: SymbolTracker) { + const type = getTypeOfSymbol(getSymbolOfDeclaration(node)); + return literalTypeToNode(type as FreshableType, node, tracker); } - function isNodeWithinClass(node: Node, classDeclaration: ClassLikeDeclaration) { - return !!forEachEnclosingClass(node, n => n === classDeclaration); + function getJsxFactoryEntity(location: Node): EntityName | undefined { + return location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity; } - function getLeftSideOfImportEqualsOrExportAssignment(nodeOnRightSide: EntityName): ImportEqualsDeclaration | ExportAssignment | undefined { - while (nodeOnRightSide.parent.kind === SyntaxKind.QualifiedName) { - nodeOnRightSide = nodeOnRightSide.parent as QualifiedName; + 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 (nodeOnRightSide.parent.kind === SyntaxKind.ImportEqualsDeclaration) { - return (nodeOnRightSide.parent as ImportEqualsDeclaration).moduleReference === nodeOnRightSide ? nodeOnRightSide.parent as ImportEqualsDeclaration : undefined; + if (compilerOptions.jsxFragmentFactory) { + return parseIsolatedEntityName(compilerOptions.jsxFragmentFactory, languageVersion); } + } - if (nodeOnRightSide.parent.kind === SyntaxKind.ExportAssignment) { - return (nodeOnRightSide.parent as ExportAssignment).expression === nodeOnRightSide as Node ? nodeOnRightSide.parent as ExportAssignment : undefined; + 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: Map; + if (resolvedTypeReferenceDirectives) { + // populate reverse mapping: file path -> type reference directive that was resolved to this file + fileToDirective = new Map(); + resolvedTypeReferenceDirectives.forEach(({ resolvedTypeReferenceDirective }, key, mode) => { + if (!resolvedTypeReferenceDirective?.resolvedFileName) { + return; + } + const file = host.getSourceFile(resolvedTypeReferenceDirective.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, mode); + } + }); } - return undefined; - } - - function isInRightSideOfImportOrExportAssignment(node: EntityName) { - return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined; - } + return { + getReferencedExportContainer, + getReferencedImportDeclaration, + getReferencedDeclarationWithCollidingName, + isDeclarationWithCollidingName, + isValueAliasDeclaration: nodeIn => { + const node = getParseTreeNode(nodeIn); + // Synthesized nodes are always treated like values. + return node && canCollectSymbolAliasAccessabilityData ? isValueAliasDeclaration(node) : true; + }, + hasGlobalName, + isReferencedAliasDeclaration: (nodeIn, checkChildren?) => { + const node = getParseTreeNode(nodeIn); + // Synthesized nodes are always treated as referenced. + return node && canCollectSymbolAliasAccessabilityData ? 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: nodeIn => { + const node = getParseTreeNode(nodeIn, hasPossibleExternalModuleReference); + return node && getExternalModuleFileFromDeclaration(node); + }, + getTypeReferenceDirectivesForEntityName, + getTypeReferenceDirectivesForSymbol, + isLiteralConstDeclaration, + isLateBound: (nodeIn: Declaration): nodeIn is LateBoundDeclaration => { + const node = getParseTreeNode(nodeIn, isDeclaration); + const symbol = node && getSymbolOfDeclaration(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(getSymbolOfDeclaration(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 = getSymbolOfDeclaration(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 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 getSymbolOfDeclaration(entityName.parent.parent as BinaryExpression); + 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); + if (merged.declarations) { + for (const d of merged.declarations) { + const declFile = getSourceFileOfNode(d); + if (declFile === importTarget) { + return true; + } + } + } + } + } + return false; } - } - 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; + function isInHeritageClause(node: PropertyAccessEntityNameExpression) { + return node.parent && node.parent.kind === SyntaxKind.ExpressionWithTypeArguments && node.parent.parent && node.parent.parent.kind === SyntaxKind.HeritageClause; } - return undefined; - } - function isThisPropertyAndThisTyped(node: PropertyAccessExpression) { - if (node.expression.kind === SyntaxKind.ThisKeyword) { - const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); - if (isFunctionLike(container)) { - const containingLiteral = getContainingObjectLiteral(container); - if (containingLiteral) { - const contextualType = getApparentTypeOfContextualType(containingLiteral, /*contextFlags*/ undefined); - const type = contextualType && getThisTypeFromContextualType(contextualType); - return type && !isTypeAny(type); + // defined here to avoid outer scope pollution + function getTypeReferenceDirectivesForEntityName(node: EntityNameOrEntityNameExpression): [specifier: string, mode: ResolutionMode][] | undefined { + // program does not have any files with type reference directives - bail out + if (!fileToDirective) { + return undefined; + } + // computed property name should use node as value + // 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; + if (node.parent.kind === SyntaxKind.ComputedPropertyName) { + meaning = SymbolFlags.Value | SymbolFlags.ExportValue; + } + else { + meaning = SymbolFlags.Type | SymbolFlags.Namespace; + if ((node.kind === SyntaxKind.Identifier && isInTypeQuery(node)) || (node.kind === SyntaxKind.PropertyAccessExpression && !isInHeritageClause(node))) { + meaning = SymbolFlags.Value | SymbolFlags.ExportValue; } } - } - } - function getSymbolOfNameOrPropertyAccessExpression(name: EntityName | PrivateIdentifier | PropertyAccessExpression | JSDocMemberName): Symbol | undefined { - if (isDeclarationName(name)) { - return getSymbolOfNode(name.parent); + const symbol = resolveEntityName(node, meaning, /*ignoreErrors*/ true); + return symbol && symbol !== unknownSymbol ? getTypeReferenceDirectivesForSymbol(symbol, meaning) : undefined; } - 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) && !isJSDocMemberName(name) && !isThisPropertyAndThisTyped(name.parent as PropertyAccessExpression)) { - const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(name); - if (specialPropertyAssignmentSymbol) { - return specialPropertyAssignmentSymbol; + // defined here to avoid outer scope pollution + function getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): [specifier: string, mode: ResolutionMode][] | undefined { + // program does not have any files with type reference directives - bail out + if (!fileToDirective || !isSymbolFromTypeDeclarationFile(symbol)) { + return undefined; + } + // check what declarations in the symbol can contribute to the target meaning + let typeReferenceDirectives: [specifier: string, mode: ResolutionMode][] | 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; } - 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; + 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; } - } - else if (isEntityName(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); + + // 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; } - if (isEntityName(name)) { - const possibleImportNode = isImportTypeQualifierPart(name); - if (possibleImportNode) { - getTypeFromTypeNode(possibleImportNode); - const sym = getNodeLinks(name).resolvedSymbol; - return sym === unknownSymbol ? undefined : sym; + function addReferencedFilesToTypeDirective(file: SourceFile, key: string, mode: ResolutionMode) { + if (fileToDirective.has(file.path)) return; + fileToDirective.set(file.path, [key, mode]); + for (const { fileName, resolutionMode } of file.referencedFiles) { + const resolvedFile = resolveTripleslashReference(fileName, file.fileName); + const referencedFile = host.getSourceFile(resolvedFile); + if (referencedFile) { + addReferencedFilesToTypeDirective(referencedFile, key, resolutionMode || file.impliedNodeFormat); + } } } + } - while (isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName(name)) { - name = name.parent as QualifiedName | PropertyAccessEntityNameExpression | JSDocMemberName; + function getExternalModuleFileFromDeclaration(declaration: AnyImportOrReExport | ModuleDeclaration | ImportTypeNode | ImportCall): 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); + } - if (isInNameOfExpressionWithTypeArguments(name)) { - let meaning = SymbolFlags.None; - if (name.parent.kind === SyntaxKind.ExpressionWithTypeArguments) { - // An 'ExpressionWithTypeArguments' may appear in type space (interface Foo extends Bar), - // value space (return foo), or both(class Foo extends Bar); ensure the meaning matches. - meaning = isPartOfTypeNode(name) ? SymbolFlags.Type : SymbolFlags.Value; + function initializeTypeChecker() { + tsPlusGlobalImportCache.clear(); + // Bind all source files and propagate errors + for (const file of host.getSourceFiles()) { + bindSourceFile(file, compilerOptions); + for (const statement of file.imports) { + if (isImportDeclaration(statement.parent)) { + tryCacheTsPlusGlobalSymbol(statement.parent); + } + } + } - // In a class 'extends' clause we are also looking for a value. - if (isExpressionWithTypeArgumentsInClassExtendsClause(name.parent)) { - meaning |= SymbolFlags.Value; + 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?.declarations) { + 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!); } - else { - meaning = SymbolFlags.Namespace; + if (file.jsGlobalAugmentations) { + mergeSymbolTable(globals, file.jsGlobalAugmentations); } - - meaning |= SymbolFlags.Alias; - const entityNameSymbol = isEntityNameExpression(name) ? resolveEntityName(name, meaning, /*ignoreErrors*/ true) : undefined; - if (entityNameSymbol) { - return entityNameSymbol; + 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); + } + }); } } - if (name.parent.kind === SyntaxKind.JSDocParameterTag) { - return getParameterSymbolFromJSDoc(name.parent as JSDocParameterTag); + // 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); + } + } } - 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; + // 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(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); } - if (isExpressionNode(name)) { - if (nodeIsMissing(name)) { - // Missing entity name. - return undefined; + globalReadonlyArrayType = getGlobalTypeOrUndefined("ReadonlyArray" as __String, /*arity*/ 1) as GenericType || globalArrayType; + anyReadonlyArrayType = globalReadonlyArrayType ? createTypeFromGenericGlobalType(globalReadonlyArrayType, [anyType]) : anyArrayType; + globalThisType = getGlobalTypeOrUndefined("ThisType" as __String, /*arity*/ 1) as GenericType; + + 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); + } } + } - const isJSDoc = findAncestor(name, or(isJSDocLinkLike, isJSDocNameReference, isJSDocMemberName)); - const meaning = isJSDoc ? SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value : SymbolFlags.Value; - if (name.kind === SyntaxKind.Identifier) { - if (isJSXTagName(name) && isJsxIntrinsicTagName(name)) { - const symbol = getIntrinsicTagSymbol(name.parent as JsxOpeningLikeElement); - return symbol === unknownSymbol ? undefined : symbol; - } - const result = resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name)); - if (!result && isJSDoc) { - const container = findAncestor(name, or(isClassLike, isInterfaceDeclaration)); - if (container) { - return resolveJSDocMemberName(name, /*ignoreErrors*/ false, getSymbolOfDeclaration(container)); + 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); } - } - if (result && isJSDoc) { - const container = getJSDocHost(name); - if (container && isEnumMember(container) && container === result.valueDeclaration) { - return resolveEntityName(name, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, getSourceFileOfNode(container)) || result; + for (const node of secondFileLocations) { + addDuplicateDeclarationError(node, message, symbolName, firstFileLocations); } - } - return result; + }); } - else if (isPrivateIdentifier(name)) { - return getSymbolForPrivateIdentifierExpression(name); + 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) + )); } - else if (name.kind === SyntaxKind.PropertyAccessExpression || name.kind === SyntaxKind.QualifiedName) { - const links = getNodeLinks(name); - if (links.resolvedSymbol) { - return links.resolvedSymbol; - } + }); + amalgamatedDuplicates = undefined; + tsPlusDebug && console.time("initTsPlusTypeChecker") + initTsPlusTypeChecker(); + tsPlusDebug && console.timeEnd("initTsPlusTypeChecker") + } - if (name.kind === SyntaxKind.PropertyAccessExpression) { - checkPropertyAccessExpression(name, CheckMode.Normal); - if (!links.resolvedSymbol) { - const expressionType = checkExpressionCached(name.expression); - const infos = getApplicableIndexInfos(expressionType, getLiteralTypeFromPropertyName(name.name)); - if (infos.length && (expressionType as ObjectType).members) { - const resolved = resolveStructuredTypeMembers(expressionType as ObjectType); - const symbol = resolved.members.get(InternalSymbolName.Index); - if (infos === getIndexInfosOfType(expressionType)) { - links.resolvedSymbol = symbol; - } - else if (symbol) { - const symbolLinks = getSymbolLinks(symbol); - const declarationList = mapDefined(infos, i => i.declaration); - const nodeListId = map(declarationList, getNodeId).join(","); - if (!symbolLinks.filteredIndexSymbolCache) { - symbolLinks.filteredIndexSymbolCache = new Map(); + 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) { + for (const name of getHelperNames(helper)) { + if (requestedExternalEmitHelperNames.has(name)) continue; + requestedExternalEmitHelperNames.add(name); + + 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); } - if (symbolLinks.filteredIndexSymbolCache.has(nodeListId)) { - links.resolvedSymbol = symbolLinks.filteredIndexSymbolCache.get(nodeListId)!; + else if (helper & ExternalEmitHelpers.ClassPrivateFieldGet) { + if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 3)) { + error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 4); + } } - else { - const copy = createSymbol(SymbolFlags.Signature, InternalSymbolName.Index); - copy.declarations = mapDefined(infos, i => i.declaration); - copy.parent = expressionType.aliasSymbol ? expressionType.aliasSymbol : expressionType.symbol ? expressionType.symbol : getSymbolAtLocation(copy.declarations[0].parent); - symbolLinks.filteredIndexSymbolCache.set(nodeListId, copy); - links.resolvedSymbol = symbolLinks.filteredIndexSymbolCache.get(nodeListId)!; + else if (helper & ExternalEmitHelpers.ClassPrivateFieldSet) { + if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 4)) { + error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 5); + } + } + else if (helper & ExternalEmitHelpers.SpreadArray) { + if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 2)) { + error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 3); + } } } } } } - else { - checkQualifiedName(name, CheckMode.Normal); - } - if (!links.resolvedSymbol && isJSDoc && isQualifiedName(name)) { - return resolveJSDocMemberName(name); - } - return links.resolvedSymbol; - } - else if (isJSDocMemberName(name)) { - return resolveJSDocMemberName(name); + requestedExternalEmitHelpers |= helpers; } } - else if (isTypeReferenceIdentifier(name as EntityName)) { - const meaning = name.parent.kind === SyntaxKind.TypeReference ? SymbolFlags.Type : SymbolFlags.Namespace; - const symbol = resolveEntityName(name as EntityName, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true); - return symbol && symbol !== unknownSymbol ? symbol : getUnresolvedSymbolForEntityName(name as EntityName); - } - if (name.parent.kind === SyntaxKind.TypePredicate) { - return resolveEntityName(name as Identifier, /*meaning*/ SymbolFlags.FunctionScopedVariable); - } - - return undefined; } - /** - * Recursively resolve entity names and jsdoc instance references: - * 1. K#m as K.prototype.m for a class (or other value) K - * 2. K.m as K.prototype.m - * 3. I.m as I.m for a type I, or any other I.m that fails to resolve in (1) or (2) - * - * For unqualified names, a container K may be provided as a second argument. - */ - function resolveJSDocMemberName(name: EntityName | JSDocMemberName, ignoreErrors?: boolean, container?: Symbol): Symbol | undefined { - if (isEntityName(name)) { - // resolve static values first - const meaning = SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value; - let symbol = resolveEntityName(name, meaning, ignoreErrors, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name)); - if (!symbol && isIdentifier(name) && container) { - symbol = getMergedSymbol(getSymbol(getExportsOfSymbol(container), name.escapedText, meaning)); - } - if (symbol) { - return symbol; - } - } - const left = isIdentifier(name) ? container : resolveJSDocMemberName(name.left, ignoreErrors, container); - const right = isIdentifier(name) ? name.escapedText : name.right.escapedText; - if (left) { - const proto = left.flags & SymbolFlags.Value && getPropertyOfType(getTypeOfSymbol(left), "prototype" as __String); - const t = proto ? getTypeOfSymbol(proto) : getDeclaredTypeOfSymbol(left); - return getPropertyOfType(t, right); + function getHelperNames(helper: ExternalEmitHelpers) { + switch (helper) { + case ExternalEmitHelpers.Extends: return ["__extends"]; + case ExternalEmitHelpers.Assign: return ["__assign"]; + case ExternalEmitHelpers.Rest: return ["__rest"]; + case ExternalEmitHelpers.Decorate: return legacyDecorators ? ["__decorate"] : ["__esDecorate", "__runInitializers"]; + 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.SpreadArray: return ["__spreadArray"]; + 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.ClassPrivateFieldIn: return ["__classPrivateFieldIn"]; + case ExternalEmitHelpers.CreateBinding: return ["__createBinding"]; + case ExternalEmitHelpers.SetFunctionName: return ["__setFunctionName"]; + case ExternalEmitHelpers.PropKey: return ["__propKey"]; + default: return Debug.fail("Unrecognized helper"); } } - function getSymbolAtLocation(node: Node, ignoreErrors?: boolean): Symbol | undefined { - if (isSourceFile(node)) { - return isExternalModule(node) ? getMergedSymbol(node.symbol) : undefined; + 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; } - const { parent } = node; - const grandParent = parent.parent; + return externalHelpersModule; + } - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return undefined; - } + // GRAMMAR CHECKING - if (isDeclarationNameOrImportPropertyName(node)) { - // This is a declaration, call getSymbolOfNode - const parentSymbol = getSymbolOfDeclaration(parent as Declaration)!; - return isImportOrExportSpecifier(node.parent) && node.parent.propertyName === node - ? getImmediateAliasedSymbol(parentSymbol) - : parentSymbol; - } - else if (isLiteralComputedPropertyDeclarationName(node)) { - return getSymbolOfDeclaration(parent.parent as Declaration); + function checkGrammarModifiers(node: HasModifiers | HasDecorators | HasIllegalModifiers | HasIllegalDecorators): boolean { + const quickResult = reportObviousDecoratorErrors(node) || reportObviousModifierErrors(node); + if (quickResult !== undefined) { + return quickResult; } - if (node.kind === SyntaxKind.Identifier) { - if (isInRightSideOfImportOrExportAssignment(node as Identifier)) { - return getSymbolOfNameOrPropertyAccessExpression(node as Identifier); - } - else if (parent.kind === SyntaxKind.BindingElement && - grandParent.kind === SyntaxKind.ObjectBindingPattern && - node === (parent as BindingElement).propertyName) { - const typeOfPattern = getTypeOfNode(grandParent); - const propertyDeclaration = getPropertyOfType(typeOfPattern, (node as Identifier).escapedText); + if (isParameter(node) && parameterIsThisKeyword(node)) { + return grammarErrorOnFirstToken(node, Diagnostics.Neither_decorators_nor_modifiers_may_be_applied_to_this_parameters); + } - if (propertyDeclaration) { - return propertyDeclaration; - } - } - else if (isMetaProperty(parent) && parent.name === node) { - if (parent.keywordToken === SyntaxKind.NewKeyword && idText(node as Identifier) === "target") { - // `target` in `new.target` - return checkNewTargetMetaProperty(parent).symbol; + let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastOverride: Node | undefined, firstDecorator: Decorator | undefined; + let flags = ModifierFlags.None; + let sawExportBeforeDecorators = false; + // We parse decorators and modifiers in four contiguous chunks: + // [...leadingDecorators, ...leadingModifiers, ...trailingDecorators, ...trailingModifiers]. It is an error to + // have both leading and trailing decorators. + let hasLeadingDecorators = false; + for (const modifier of (node as HasModifiers).modifiers!) { + if (isDecorator(modifier)) { + if (!nodeCanBeDecorated(legacyDecorators, 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); + } } - // The `meta` in `import.meta` could be given `getTypeOfNode(parent).symbol` (the `ImportMeta` interface symbol), but - // we have a fake expression type made for other reasons already, whose transient `meta` - // member should more exactly be the kind of (declarationless) symbol we want. - // (See #44364 and #45031 for relevant implementation PRs) - if (parent.keywordToken === SyntaxKind.ImportKeyword && idText(node as Identifier) === "meta") { - return getGlobalImportMetaExpressionType().members!.get("meta" as __String); + else if (legacyDecorators && (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor)) { + const accessors = getAllAccessorDeclarations((node.parent as ClassDeclaration).members, node as AccessorDeclaration); + if (hasDecorators(accessors.firstAccessor) && node === accessors.secondAccessor) { + return grammarErrorOnFirstToken(node, Diagnostics.Decorators_cannot_be_applied_to_multiple_get_Slashset_accessors_of_the_same_name); + } } - // no other meta properties are valid syntax, thus no others should have symbols - return undefined; - } - } - switch (node.kind) { - case SyntaxKind.Identifier: - case SyntaxKind.PrivateIdentifier: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.QualifiedName: - if (!isThisInTypeQuery(node)) { - return getSymbolOfNameOrPropertyAccessExpression(node as EntityName | PrivateIdentifier | PropertyAccessExpression); + // if we've seen any modifiers aside from `export`, `default`, or another decorator, then this is an invalid position + if (flags & ~(ModifierFlags.ExportDefault | ModifierFlags.Decorator)) { + return grammarErrorOnNode(modifier, Diagnostics.Decorators_are_not_valid_here); } - // falls through - case SyntaxKind.ThisKeyword: - const container = getThisContainer(node, /*includeArrowFunctions*/ false, /*includeClassComputedPropertyName*/ false); - if (isFunctionLike(container)) { - const sig = getSignatureFromDeclaration(container); - if (sig.thisParameter) { - return sig.thisParameter; + // if we've already seen leading decorators and leading modifiers, then trailing decorators are an invalid position + if (hasLeadingDecorators && flags & ModifierFlags.Modifier) { + Debug.assertIsDefined(firstDecorator); + const sourceFile = getSourceFileOfNode(modifier); + if (!hasParseDiagnostics(sourceFile)) { + addRelatedInfo( + error(modifier, Diagnostics.Decorators_may_not_appear_after_export_or_export_default_if_they_also_appear_before_export), + createDiagnosticForNode(firstDecorator, Diagnostics.Decorator_used_before_export_here)); + return true; } + return false; } - 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 as ClassDeclaration).symbol; - } - return undefined; + flags |= ModifierFlags.Decorator; - 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 as ImportDeclaration).moduleSpecifier === node) || - ((isInJSFile(node) && getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.Bundler && isRequireCall(node.parent, /*requireStringLiteralLikeArgument*/ false)) || isImportCall(node.parent)) || - (isLiteralTypeNode(node.parent) && isLiteralImportTypeNode(node.parent.parent) && node.parent.parent.argument === node.parent) - ) { - return resolveExternalModuleName(node, node as LiteralExpression, ignoreErrors); + // if we have not yet seen a modifier, then these are leading decorators + if (!(flags & ModifierFlags.Modifier)) { + hasLeadingDecorators = true; } - if (isCallExpression(parent) && isBindableObjectDefinePropertyCall(parent) && parent.arguments[1] === node) { - return getSymbolOfDeclaration(parent); + else if (flags & ModifierFlags.Export) { + sawExportBeforeDecorators = true; } - // 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; - case SyntaxKind.ImportKeyword: - case SyntaxKind.NewKeyword: - return isMetaProperty(node.parent) ? checkMetaPropertyKeyword(node.parent).symbol : undefined; - case SyntaxKind.MetaProperty: - return checkExpression(node as Expression).symbol; - case SyntaxKind.JsxNamespacedName: - if (isJSXTagName(node) && isJsxIntrinsicTagName(node)) { - const symbol = getIntrinsicTagSymbol(node.parent as JsxOpeningLikeElement); - return symbol === unknownSymbol ? undefined : symbol; + firstDecorator ??= modifier; + } + else { + 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 && (modifier.kind !== SyntaxKind.StaticKeyword || !isClassLike(node.parent))) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_index_signature, tokenToString(modifier.kind)); + } } - // falls through - - default: - return undefined; - } - } - - function getIndexInfosAtLocation(node: Node): readonly IndexInfo[] | undefined { - if (isIdentifier(node) && isPropertyAccessExpression(node.parent) && node.parent.name === node) { - const keyType = getLiteralTypeFromPropertyName(node); - const objectType = getTypeOfExpression(node.parent.expression); - const objectTypes = objectType.flags & TypeFlags.Union ? (objectType as UnionType).types : [objectType]; - return flatMap(objectTypes, t => filter(getIndexInfosOfType(t), info => isApplicableIndexType(keyType, info.keyType))); - } - return undefined; - } + if (modifier.kind !== SyntaxKind.InKeyword && modifier.kind !== SyntaxKind.OutKeyword && modifier.kind !== SyntaxKind.ConstKeyword) { + if (node.kind === SyntaxKind.TypeParameter) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_parameter, tokenToString(modifier.kind)); + } + } + switch (modifier.kind) { + case SyntaxKind.ConstKeyword: + if (node.kind !== SyntaxKind.EnumDeclaration && node.kind !== SyntaxKind.TypeParameter) { + return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword)); + } + const parent = node.parent; + if (node.kind === SyntaxKind.TypeParameter && !(isFunctionLikeDeclaration(parent) || isClassLike(parent) || isFunctionTypeNode(parent) || + isConstructorTypeNode(parent) || isCallSignatureDeclaration(parent) || isConstructSignatureDeclaration(parent) || isMethodSignature(parent))) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_can_only_appear_on_a_type_parameter_of_a_function_method_or_class, tokenToString(modifier.kind)); + } + break; + case SyntaxKind.OverrideKeyword: + // If node.kind === SyntaxKind.Parameter, checkParameter reports an error if it's not a parameter property. + if (flags & ModifierFlags.Override) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "override"); + } + else if (flags & ModifierFlags.Ambient) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "override", "declare"); + } + else if (flags & ModifierFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "override", "readonly"); + } + else if (flags & ModifierFlags.Accessor) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "override", "accessor"); + } + else if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "override", "async"); + } + flags |= ModifierFlags.Override; + lastOverride = modifier; + break; - function getShorthandAssignmentValueSymbol(location: Node | undefined): Symbol | undefined { - if (location && location.kind === SyntaxKind.ShorthandPropertyAssignment) { - return resolveEntityName((location as ShorthandPropertyAssignment).name, SymbolFlags.Value | SymbolFlags.Alias); - } - return undefined; - } + case SyntaxKind.PublicKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.PrivateKeyword: + const text = visibilityToString(modifierToFlag(modifier.kind)); - /** 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); - } - } + if (flags & ModifierFlags.AccessibilityModifier) { + return grammarErrorOnNode(modifier, Diagnostics.Accessibility_modifier_already_seen); + } + else if (flags & ModifierFlags.Override) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "override"); + } + else if (flags & ModifierFlags.Static) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "static"); + } + else if (flags & ModifierFlags.Accessor) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "accessor"); + } + 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 (isPrivateIdentifierClassElementDeclaration(node)) { + return grammarErrorOnNode(modifier, Diagnostics.An_accessibility_modifier_cannot_be_used_with_a_private_identifier); + } + flags |= modifierToFlag(modifier.kind); + break; - function getTypeOfNode(node: Node): Type { - if (isSourceFile(node) && !isExternalModule(node)) { - return errorType; - } + 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 (flags & ModifierFlags.Accessor) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "accessor"); + } + 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 (flags & ModifierFlags.Override) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "override"); + } + flags |= ModifierFlags.Static; + lastStatic = modifier; + break; - if (node.flags & NodeFlags.InWithStatement) { - // We cannot answer semantic questions within a with block, do not proceed any further - return errorType; - } + case SyntaxKind.AccessorKeyword: + if (flags & ModifierFlags.Accessor) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "accessor"); + } + else if (flags & ModifierFlags.Readonly) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "accessor", "readonly"); + } + else if (flags & ModifierFlags.Ambient) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "accessor", "declare"); + } + else if (node.kind !== SyntaxKind.PropertyDeclaration) { + return grammarErrorOnNode(modifier, Diagnostics.accessor_modifier_can_only_appear_on_a_property_declaration); + } - const classDecl = tryGetClassImplementingOrExtendingExpressionWithTypeArguments(node); - const classType = classDecl && getDeclaredTypeOfClassOrInterface(getSymbolOfDeclaration(classDecl.class)); - if (isPartOfTypeNode(node)) { - const typeFromTypeNode = getTypeFromTypeNode(node as TypeNode); - return classType ? getTypeWithThisArgument(typeFromTypeNode, classType.thisType) : typeFromTypeNode; - } + flags |= ModifierFlags.Accessor; + break; - if (isExpressionNode(node)) { - return getRegularTypeOfExpression(node as Expression); - } + 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 reports 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); + } + else if (flags & ModifierFlags.Accessor) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "readonly", "accessor"); + } + flags |= ModifierFlags.Readonly; + break; - 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; - } + case SyntaxKind.ExportKeyword: + if (compilerOptions.verbatimModuleSyntax && + !(node.flags & NodeFlags.Ambient) && + node.kind !== SyntaxKind.TypeAliasDeclaration && + node.kind !== SyntaxKind.InterfaceDeclaration && + // ModuleDeclaration needs to be checked that it is uninstantiated later + node.kind !== SyntaxKind.ModuleDeclaration && + node.parent.kind === SyntaxKind.SourceFile && + (moduleKind === ModuleKind.CommonJS || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) + ) { + return grammarErrorOnNode(modifier, Diagnostics.A_top_level_export_modifier_cannot_be_used_on_value_declarations_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); + } + 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); + } + else if (!(flags & ModifierFlags.Export)) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "default"); + } + else if (sawExportBeforeDecorators) { + return grammarErrorOnNode(firstDecorator!, Diagnostics.Decorators_are_not_valid_here); + } - if (isTypeDeclaration(node)) { - // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration - const symbol = getSymbolOfDeclaration(node); - return getDeclaredTypeOfSymbol(symbol); - } + 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 (flags & ModifierFlags.Override) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "override"); + } + 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 (isPrivateIdentifierClassElementDeclaration(node)) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "declare"); + } + else if (flags & ModifierFlags.Accessor) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "declare", "accessor"); + } + flags |= ModifierFlags.Ambient; + lastDeclare = modifier; + break; - if (isTypeDeclarationName(node)) { - const symbol = getSymbolAtLocation(node); - return symbol ? getDeclaredTypeOfSymbol(symbol) : errorType; - } + case SyntaxKind.AbstractKeyword: + if (flags & ModifierFlags.Abstract) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "abstract"); + } + if (node.kind !== SyntaxKind.ClassDeclaration && + node.kind !== SyntaxKind.ConstructorType) { + 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))) { + const message = node.kind === SyntaxKind.PropertyDeclaration + ? Diagnostics.Abstract_properties_can_only_appear_within_an_abstract_class + : Diagnostics.Abstract_methods_can_only_appear_within_an_abstract_class; + return grammarErrorOnNode(modifier, message); + } + 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 (flags & ModifierFlags.Override) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "abstract", "override"); + } + if (flags & ModifierFlags.Accessor) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "abstract", "accessor"); + } + } + if (isNamedDeclaration(node) && node.name.kind === SyntaxKind.PrivateIdentifier) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "abstract"); + } - if (isBindingElement(node)) { - return getTypeForVariableLikeDeclaration(node, /*includeOptionality*/ true, CheckMode.Normal) || errorType; - } + flags |= ModifierFlags.Abstract; + break; - if (isDeclaration(node)) { - // In this case, we call getSymbolOfNode instead of getSymbolAtLocation because it is a declaration - const symbol = getSymbolOfDeclaration(node); - return symbol ? getTypeOfSymbol(symbol) : errorType; - } + 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 (isDeclarationNameOrImportPropertyName(node)) { - const symbol = getSymbolAtLocation(node); - if (symbol) { - return getTypeOfSymbol(symbol); + case SyntaxKind.InKeyword: + case SyntaxKind.OutKeyword: + const inOutFlag = modifier.kind === SyntaxKind.InKeyword ? ModifierFlags.In : ModifierFlags.Out; + const inOutText = modifier.kind === SyntaxKind.InKeyword ? "in" : "out"; + if (node.kind !== SyntaxKind.TypeParameter || !(isInterfaceDeclaration(node.parent) || isClassLike(node.parent) || isTypeAliasDeclaration(node.parent))) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_can_only_appear_on_a_type_parameter_of_a_class_interface_or_type_alias, inOutText); + } + if (flags & inOutFlag) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, inOutText); + } + if (inOutFlag & ModifierFlags.In && flags & ModifierFlags.Out) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "in", "out"); + } + flags |= inOutFlag; + break; + } } - return errorType; - } - - if (isBindingPattern(node)) { - return getTypeForVariableLikeDeclaration(node.parent, /*includeOptionality*/ true, CheckMode.Normal) || errorType; } - if (isInRightSideOfImportOrExportAssignment(node as Identifier)) { - const symbol = getSymbolAtLocation(node); - if (symbol) { - const declaredType = getDeclaredTypeOfSymbol(symbol); - return !isErrorType(declaredType) ? declaredType : getTypeOfSymbol(symbol); + 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.Override) { + return grammarErrorOnNode(lastOverride!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "override"); // TODO: GH#18217 + } + if (flags & ModifierFlags.Async) { + return grammarErrorOnNode(lastAsync!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async"); + } + return false; } - - if (isMetaProperty(node.parent) && node.parent.keywordToken === node.kind) { - return checkMetaPropertyKeyword(node.parent); - } - - 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 as ForOfStatement); - return checkDestructuringAssignment(expr, iteratedType || errorType); + 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"); } - // If this is from "for" initializer - // for ({a } = elems[0];.....) { } - if (expr.parent.kind === SyntaxKind.BinaryExpression) { - const iteratedType = getTypeOfExpression((expr.parent as BinaryExpression).right); - return checkDestructuringAssignment(expr, iteratedType || errorType); + 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); } - // 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); + 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); } - // 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 as Expression; + if (flags & ModifierFlags.Async) { + return checkGrammarAsyncModifier(node, lastAsync!); } - return getRegularTypeOfLiteralType(getTypeOfExpression(expr)); + return false; } /** - * Gets either the static or instance type of a class element, based on - * whether the element is declared as "static". + * true | false: Early return this value from checkGrammarModifiers. + * undefined: Need to do full checking on the modifiers. */ - function getParentTypeOfClassElement(node: ClassElement) { - const classSymbol = getSymbolOfNode(node.parent)!; - return isStatic(node) - ? getTypeOfSymbol(classSymbol) - : getDeclaredTypeOfSymbol(classSymbol); - } + function reportObviousModifierErrors(node: HasModifiers | HasIllegalModifiers): boolean | undefined { + if (!node.modifiers) return false; - function getClassElementPropertyKeyType(element: ClassElement) { - const name = element.name!; - switch (name.kind) { - case SyntaxKind.Identifier: - return getStringLiteralType(idText(name)); - case SyntaxKind.NumericLiteral: - case SyntaxKind.StringLiteral: - return getStringLiteralType(name.text); - case SyntaxKind.ComputedPropertyName: - const nameType = checkComputedPropertyName(name); - return isTypeAssignableToKind(nameType, TypeFlags.ESSymbolLike) ? nameType : stringType; - default: - return Debug.fail("Unsupported property name."); - } + const modifier = findFirstIllegalModifier(node); + return modifier && grammarErrorOnFirstToken(modifier, Diagnostics.Modifiers_cannot_appear_here); } - // 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); - } - }); + function findFirstModifierExcept(node: HasModifiers, allowedModifier: SyntaxKind): Modifier | undefined { + const modifier = find(node.modifiers, isModifier); + return modifier && modifier.kind !== allowedModifier ? modifier : undefined; } - return getNamedMembers(propsByName); - } - - function typeHasCallOrConstructSignatures(type: Type): boolean { - return getSignaturesOfType(type, SignatureKind.Call).length !== 0 || getSignaturesOfType(type, SignatureKind.Construct).length !== 0; - } - 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 { links: { leftSpread, rightSpread, syntheticOrigin } } = symbol as TransientSymbol; - return leftSpread ? [leftSpread, rightSpread!] - : syntheticOrigin ? [syntheticOrigin] - : singleElementArray(tryGetTarget(symbol)); - } - return undefined; - } - function tryGetTarget(symbol: Symbol): Symbol | undefined { - let target: Symbol | undefined; - let next: Symbol | undefined = symbol; - while (next = getSymbolLinks(next).target) { - target = next; + function findFirstIllegalModifier(node: HasModifiers | HasIllegalModifiers): Modifier | undefined { + 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: + case SyntaxKind.TypeParameter: + return undefined; + case SyntaxKind.ClassStaticBlockDeclaration: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.NamespaceExportDeclaration: + case SyntaxKind.MissingDeclaration: + return find(node.modifiers, isModifier); + default: + if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { + return undefined; + } + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + return findFirstModifierExcept(node, SyntaxKind.AsyncKeyword); + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ConstructorType: + return findFirstModifierExcept(node, SyntaxKind.AbstractKeyword); + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.VariableStatement: + case SyntaxKind.TypeAliasDeclaration: + return find(node.modifiers, isModifier); + case SyntaxKind.EnumDeclaration: + return findFirstModifierExcept(node, SyntaxKind.ConstKeyword); + default: + Debug.assertNever(node); + } } - 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 && !!(getAllSymbolFlags(s) & SymbolFlags.Value); - } + function reportObviousDecoratorErrors(node: HasModifiers | HasDecorators | HasIllegalModifiers | HasIllegalDecorators) { + const decorator = findFirstIllegalDecorator(node); + return decorator && grammarErrorOnFirstToken(decorator, Diagnostics.Decorators_are_not_valid_here); } - function isNameOfModuleOrEnumDeclaration(node: Identifier) { - return isModuleOrEnumDeclaration(node.parent) && node === node.parent.name; + function findFirstIllegalDecorator(node: HasModifiers | HasDecorators | HasIllegalModifiers | HasIllegalDecorators): Decorator | undefined { + return canHaveIllegalDecorators(node) ? find(node.modifiers, isDecorator) : undefined; } - // 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 as SourceFile; - 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) && getSymbolOfDeclaration(n) === parentSymbol); - } - } + 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"); } - // 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 { - const specifier = getIdentifierGeneratedImportReference(nodeIn); - if (specifier) { - return specifier; + 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); } - const node = getParseTreeNode(nodeIn, isIdentifier); - if (node) { - const symbol = getReferencedValueOrAliasSymbol(node); + return false; + } - // 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, SymbolFlags.Value)) { - return getDeclarationOfAliasSymbol(symbol); - } + 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 undefined; + return false; } - function isSymbolOfDestructuredElementOfCatchBinding(symbol: Symbol) { - return symbol.valueDeclaration - && isBindingElement(symbol.valueDeclaration) - && walkUpBindingElementsAndPatterns(symbol.valueDeclaration).parent.kind === SyntaxKind.CatchClause; - } + function checkGrammarParameterList(parameters: NodeArray) { + let seenOptionalParameter = false; + const parameterCount = parameters.length; - function isSymbolOfDeclarationWithCollidingName(symbol: Symbol): boolean { - if (symbol.flags & SymbolFlags.BlockScoped && symbol.valueDeclaration && !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); + 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); + } - links.isDeclarationWithCollidingName = !isBlockScopedContainerTopLevel(container) && (!isDeclaredInLoop || (!inLoopInitializer && !inLoopBodyBlock)); - } - else { - links.isDeclarationWithCollidingName = false; - } + if (parameter.questionToken) { + return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_rest_parameter_cannot_be_optional); } - } - 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; + 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); + } } + } - return undefined; + function getNonSimpleParameters(parameters: readonly ParameterDeclaration[]): readonly ParameterDeclaration[] { + return filter(parameters, parameter => !!parameter.initializer || isBindingPattern(parameter.name) || isRestParameter(parameter)); } - // 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 = getSymbolOfDeclaration(node); - if (symbol) { - return isSymbolOfDeclarationWithCollidingName(symbol); + 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 isValueAliasDeclaration(node: Node): boolean { - Debug.assert(canCollectSymbolAliasAccessabilityData); - switch (node.kind) { - case SyntaxKind.ImportEqualsDeclaration: - return isAliasResolvedToValue(getSymbolOfDeclaration(node as ImportEqualsDeclaration)); - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - const symbol = getSymbolOfDeclaration(node as ImportClause | NamespaceImport | ImportSpecifier | ExportSpecifier); - return !!symbol && isAliasResolvedToValue(symbol) && !getTypeOnlyAliasDeclaration(symbol, SymbolFlags.Value); - case SyntaxKind.ExportDeclaration: - const exportClause = (node as ExportDeclaration).exportClause; - return !!exportClause && ( - isNamespaceExport(exportClause) || - some(exportClause.elements, isValueAliasDeclaration) - ); - case SyntaxKind.ExportAssignment: - return (node as ExportAssignment).expression && (node as ExportAssignment).expression.kind === SyntaxKind.Identifier ? - isAliasResolvedToValue(getSymbolOfDeclaration(node as ExportAssignment)) : - true; - } - return false; + function checkGrammarFunctionLikeDeclaration(node: FunctionLikeDeclaration | MethodSignature): boolean { + // Prevent cascading error by short-circuit + const file = getSourceFileOfNode(node); + return checkGrammarModifiers(node) || + checkGrammarTypeParameterList(node.typeParameters, file) || + checkGrammarParameterList(node.parameters) || + checkGrammarArrowFunction(node, file) || + (isFunctionLikeDeclaration(node) && checkGrammarForUseStrictSimpleParameterList(node)); } - 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(getSymbolOfDeclaration(node)); - return isValue && node.moduleReference && !nodeIsMissing(node.moduleReference); + function checkGrammarClassLikeDeclaration(node: ClassLikeDeclaration): boolean { + const file = getSourceFileOfNode(node); + return checkGrammarClassDeclarationHeritageClauses(node) || + checkGrammarTypeParameterList(node.typeParameters, file); } - function isAliasResolvedToValue(symbol: Symbol | undefined): boolean { - if (!symbol) { + function checkGrammarArrowFunction(node: Node, file: SourceFile): boolean { + if (!isArrowFunction(node)) { return false; } - const target = getExportSymbolOfValueSymbolIfExported(resolveAlias(symbol)); - if (target === unknownSymbol) { - return true; + + if (node.typeParameters && !(length(node.typeParameters) > 1 || node.typeParameters.hasTrailingComma || node.typeParameters[0].constraint)) { + if (file && fileExtensionIsOneOf(file.fileName, [Extension.Mts, Extension.Cts])) { + grammarErrorOnNode(node.typeParameters[0], Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Add_a_trailing_comma_or_explicit_constraint); + } } - // 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 !!((getAllSymbolFlags(target) ?? -1) & SymbolFlags.Value) && - (shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target)); - } - function isConstEnumOrConstEnumOnlyModule(s: Symbol): boolean { - return isConstEnumSymbol(s) || !!s.constEnumOnlyModule; + 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 isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean { - Debug.assert(canCollectSymbolAliasAccessabilityData); - if (isAliasSymbolDeclaration(node)) { - const symbol = getSymbolOfDeclaration(node as Declaration); - const links = symbol && getSymbolLinks(symbol); - if (links?.referenced) { - return true; + 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); } - const target = getSymbolLinks(symbol).aliasTarget; - if (target && getEffectiveModifierFlags(node) & ModifierFlags.Export && - getAllSymbolFlags(target) & SymbolFlags.Value && - (shouldPreserveConstEnums(compilerOptions) || !isConstEnumOrConstEnumOnlyModule(target))) { - // An `export import ... =` of a value symbol is always considered referenced - return true; + else { + return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_exactly_one_parameter); } } - - if (checkChildren) { - return !!forEachChild(node, node => isReferencedAliasDeclaration(node, checkChildren)); + 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); + } + const type = getTypeFromTypeNode(parameter.type); + if (someType(type, t => !!(t.flags & TypeFlags.StringOrNumberLiteralOrUnique)) || isGenericType(type)) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_cannot_be_a_literal_type_or_generic_type_Consider_using_a_mapped_object_type_instead); + } + if (!everyType(type, isValidIndexKeyType)) { + return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_must_be_string_number_symbol_or_a_template_literal_type); + } + if (!node.type) { + return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_a_type_annotation); } 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 = getSymbolOfDeclaration(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); + function checkGrammarIndexSignature(node: IndexSignatureDeclaration) { + // Prevent cascading error by short-circuit + return checkGrammarModifiers(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 isRequiredInitializedParameter(parameter: ParameterDeclaration | JSDocParameterTag): boolean { - return !!strictNullChecks && - !isOptionalParameter(parameter) && - !isJSDocParameterTag(parameter) && - !!parameter.initializer && - !hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); + function checkGrammarTypeArguments(node: Node, typeArguments: NodeArray | undefined): boolean { + return checkGrammarForDisallowedTrailingComma(typeArguments) || + checkGrammarForAtLeastOneTypeArgument(node, typeArguments); } - function isOptionalUninitializedParameterProperty(parameter: ParameterDeclaration) { - return strictNullChecks && - isOptionalParameter(parameter) && - !parameter.initializer && - hasSyntacticModifier(parameter, ModifierFlags.ParameterPropertyModifier); + 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 isExpandoFunctionDeclaration(node: Declaration): boolean { - const declaration = getParseTreeNode(node, isFunctionDeclaration); - if (!declaration) { - return false; + function checkGrammarHeritageClause(node: HeritageClause): boolean { + const types = node.types; + if (checkGrammarForDisallowedTrailingComma(types)) { + return true; } - const symbol = getSymbolOfDeclaration(declaration); - if (!symbol || !(symbol.flags & SymbolFlags.Function)) { - return false; + if (types && types.length === 0) { + const listType = tokenToString(node.token); + return grammarErrorAtPos(node, types.pos, 0, Diagnostics._0_list_cannot_be_empty, listType); } - return !!forEachEntry(getExportsOfSymbol(symbol), p => p.flags & SymbolFlags.Value && p.valueDeclaration && isPropertyAccessExpression(p.valueDeclaration)); + return some(types, checkGrammarExpressionWithTypeArguments); } - function getPropertiesOfContainerFunction(node: Declaration): Symbol[] { - const declaration = getParseTreeNode(node, isFunctionDeclaration); - if (!declaration) { - return emptyArray; + function checkGrammarExpressionWithTypeArguments(node: ExpressionWithTypeArguments | TypeQueryNode) { + if (isExpressionWithTypeArguments(node) && isImportKeyword(node.expression) && node.typeArguments) { + return grammarErrorOnNode(node, Diagnostics.This_use_of_import_is_invalid_import_calls_can_be_written_but_they_must_have_parentheses_and_cannot_have_type_arguments); } - const symbol = getSymbolOfDeclaration(declaration); - return symbol && getPropertiesOfType(getTypeOfSymbol(symbol)) || emptyArray; + return checkGrammarTypeArguments(node, node.typeArguments); } - function getNodeCheckFlags(node: Node): NodeCheckFlags { - const nodeId = node.id || 0; - if (nodeId < 0 || nodeId >= nodeLinks.length) return 0; - return nodeLinks[nodeId]?.flags || 0; - } + function checkGrammarClassDeclarationHeritageClauses(node: ClassLikeDeclaration) { + let seenExtendsClause = false; + let seenImplementsClause = false; - function getEnumMemberValue(node: EnumMember): string | number | undefined { - computeEnumMemberValues(node.parent); - return getNodeLinks(node).enumMemberValue; - } + if (!checkGrammarModifiers(node) && node.heritageClauses) { + for (const heritageClause of node.heritageClauses) { + if (heritageClause.token === SyntaxKind.ExtendsKeyword) { + if (seenExtendsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); + } - function canHaveConstantValue(node: Node): node is EnumMember | AccessExpression { - switch (node.kind) { - case SyntaxKind.EnumMember: - case SyntaxKind.PropertyAccessExpression: - case SyntaxKind.ElementAccessExpression: - return true; - } - return false; - } + if (seenImplementsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_must_precede_implements_clause); + } - function getConstantValue(node: EnumMember | AccessExpression): string | number | undefined { - if (node.kind === SyntaxKind.EnumMember) { - return getEnumMemberValue(node); - } + if (heritageClause.types.length > 1) { + return grammarErrorOnFirstToken(heritageClause.types[1], Diagnostics.Classes_can_only_extend_a_single_class); + } - 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); - } - } + seenExtendsClause = true; + } + else { + Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); + if (seenImplementsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.implements_clause_already_seen); + } - return undefined; - } + seenImplementsClause = true; + } - function isFunctionType(type: Type): boolean { - return !!(type.flags & TypeFlags.Object) && getSignaturesOfType(type, SignatureKind.Call).length > 0; + // Grammar checking heritageClause inside class declaration + checkGrammarHeritageClause(heritageClause); + } + } } - 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; - } + function checkGrammarInterfaceDeclaration(node: InterfaceDeclaration) { + let seenExtendsClause = false; - // Resolve the symbol as a value to ensure the type can be reached at runtime during emit. - let isTypeOnly = false; - if (isQualifiedName(typeName)) { - const rootValueSymbol = resolveEntityName(getFirstIdentifier(typeName), SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location); - isTypeOnly = !!rootValueSymbol?.declarations?.every(isTypeOnlyImportOrExportDeclaration); - } - const valueSymbol = resolveEntityName(typeName, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, location); - const resolvedSymbol = valueSymbol && valueSymbol.flags & SymbolFlags.Alias ? resolveAlias(valueSymbol) : valueSymbol; - isTypeOnly ||= !!(valueSymbol && getTypeOnlyAliasDeclaration(valueSymbol, SymbolFlags.Value)); + if (node.heritageClauses) { + for (const heritageClause of node.heritageClauses) { + if (heritageClause.token === SyntaxKind.ExtendsKeyword) { + if (seenExtendsClause) { + return grammarErrorOnFirstToken(heritageClause, Diagnostics.extends_clause_already_seen); + } - // 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; - } + seenExtendsClause = true; + } + else { + Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); + return grammarErrorOnFirstToken(heritageClause, Diagnostics.Interface_declaration_cannot_have_implements_clause); + } - const constructorType = getTypeOfSymbol(resolvedSymbol); - if (constructorType && isConstructorType(constructorType)) { - return isTypeOnly ? TypeReferenceSerializationKind.TypeWithCallSignature : TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue; + // Grammar checking heritageClause inside class declaration + checkGrammarHeritageClause(heritageClause); } } + return false; + } - // 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 (isErrorType(type)) { - 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; + function checkGrammarComputedPropertyName(node: Node): boolean { + // If node is not a computedPropertyName, just skip the grammar checking + if (node.kind !== SyntaxKind.ComputedPropertyName) { + return false; } - else { - return TypeReferenceSerializationKind.ObjectType; + + const computedPropertyName = node as ComputedPropertyName; + if (computedPropertyName.expression.kind === SyntaxKind.BinaryExpression && (computedPropertyName.expression as BinaryExpression).operatorToken.kind === SyntaxKind.CommaToken) { + return grammarErrorOnNode(computedPropertyName.expression, Diagnostics.A_comma_expression_is_not_allowed_in_a_computed_property_name); } + return false; } - 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 = getSymbolOfDeclaration(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); + 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); + } } - 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 checkGrammarForInvalidQuestionMark(questionToken: QuestionToken | undefined, message: DiagnosticMessage): boolean { + return !!questionToken && grammarErrorOnNode(questionToken, message); } - 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 checkGrammarForInvalidExclamationToken(exclamationToken: ExclamationToken | undefined, message: DiagnosticMessage): boolean { + return !!exclamationToken && grammarErrorOnNode(exclamationToken, message); } - function hasGlobalName(name: string): boolean { - return globals.has(escapeLeadingUnderscores(name)); - } + function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) { + const seen = new Map<__String, DeclarationMeaning>(); - function getReferencedValueSymbol(reference: Identifier, startInDeclarationContainer?: boolean): Symbol | undefined { - const resolvedSymbol = getNodeLinks(reference).resolvedSymbol; - if (resolvedSymbol) { - return resolvedSymbol; - } + 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); + } - 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); + if (prop.kind === SyntaxKind.ShorthandPropertyAssignment && !inDestructuring && prop.objectAssignmentInitializer) { + // having objectAssignmentInitializer is only valid in ObjectAssignmentPattern + // outside of destructuring it is a syntax error + 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); } - } - return resolveName(location, reference.escapedText, SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ true); - } + if (name.kind === SyntaxKind.PrivateIdentifier) { + grammarErrorOnNode(name, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); + } - /** - * Get either a value-meaning symbol or an alias symbol. - * Unlike `getReferencedValueSymbol`, if the cached resolved symbol is the unknown symbol, - * we call `resolveName` to find a symbol. - * This is because when caching the resolved symbol, we only consider value symbols, but here - * we want to also get an alias symbol if one exists. - */ - function getReferencedValueOrAliasSymbol(reference: Identifier): Symbol | undefined { - const resolvedSymbol = getNodeLinks(reference).resolvedSymbol; - if (resolvedSymbol && resolvedSymbol !== unknownSymbol) { - return resolvedSymbol; - } + // Modifiers are never allowed on properties except for 'async' on a method declaration + if (canHaveModifiers(prop) && prop.modifiers) { + for (const mod of prop.modifiers) { + if (isModifier(mod) && (mod.kind !== SyntaxKind.AsyncKeyword || prop.kind !== SyntaxKind.MethodDeclaration)) { + grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); + } + } + } + else if (canHaveIllegalModifiers(prop) && prop.modifiers) { + for (const mod of prop.modifiers) { + if (isModifier(mod)) { + grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); + } + } + } - return resolveName( - reference, - reference.escapedText, - SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Alias, - /*nameNotFoundMessage*/ undefined, - /*nameArg*/ undefined, - /*isUse*/ true, - /*excludeGlobals*/ undefined, - /*getSpellingSuggestions*/ undefined); - } + // 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: + case SyntaxKind.PropertyAssignment: + // Grammar checking for computedPropertyName and shorthandPropertyAssignment + checkGrammarForInvalidExclamationToken(prop.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context); + 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: + Debug.assertNever(prop, "Unexpected syntax kind:" + (prop as Node).kind); + } - 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; + if (!inDestructuring) { + const effectiveName = getEffectivePropertyNameForPropertyNameNode(name); + if (effectiveName === undefined) { + continue; + } + + const existingKind = seen.get(effectiveName); + if (!existingKind) { + seen.set(effectiveName, currentKind); + } + else { + if ((currentKind & DeclarationMeaning.Method) && (existingKind & DeclarationMeaning.Method)) { + grammarErrorOnNode(name, Diagnostics.Duplicate_identifier_0, getTextOfNode(name)); + } + else if ((currentKind & DeclarationMeaning.PropertyAssignment) && (existingKind & DeclarationMeaning.PropertyAssignment)) { + grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_multiple_properties_with_the_same_name, 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); + } } } } - - return undefined; } - function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean { - if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) { - return isFreshLiteralType(getTypeOfSymbol(getSymbolOfDeclaration(node))); + function checkGrammarJsxElement(node: JsxOpeningLikeElement) { + checkGrammarJsxName(node.tagName); + 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; + const escapedText = getEscapedTextOfJsxAttributeName(name); + if (!seen.get(escapedText)) { + seen.set(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); + } } - return false; } - function literalTypeToNode(type: FreshableType, enclosing: Node, tracker: SymbolTracker): Expression { - const enumResult = type.flags & TypeFlags.EnumLike ? 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 checkGrammarJsxName(node: JsxTagNameExpression) { + if (isPropertyAccessExpression(node) && isJsxNamespacedName(node.expression)) { + return grammarErrorOnNode(node.expression, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names); + } + if (isJsxNamespacedName(node) && getJSXTransformEnabled(compilerOptions) && !isIntrinsicJsxName(node.namespace.escapedText)) { + return grammarErrorOnNode(node, Diagnostics.React_components_cannot_include_JSX_namespace_names); + } } - function createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, tracker: SymbolTracker) { - const type = getTypeOfSymbol(getSymbolOfDeclaration(node)); - return literalTypeToNode(type as FreshableType, node, tracker); + 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 getJsxFactoryEntity(location: Node): EntityName | undefined { - return location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity; - } + function checkGrammarForInOrForOfStatement(forInOrOfStatement: ForInOrOfStatement): boolean { + if (checkGrammarStatementInAmbientContext(forInOrOfStatement)) { + return true; + } - function getJsxFragmentFactoryEntity(location: Node): EntityName | undefined { - if (location) { - const file = getSourceFileOfNode(location); - if (file) { - if (file.localJsxFragmentFactory) { - return file.localJsxFragmentFactory; + if (forInOrOfStatement.kind === SyntaxKind.ForOfStatement && forInOrOfStatement.awaitModifier) { + if (!(forInOrOfStatement.flags & NodeFlags.AwaitContext)) { + const sourceFile = getSourceFileOfNode(forInOrOfStatement); + if (isInTopLevelContext(forInOrOfStatement)) { + if (!hasParseDiagnostics(sourceFile)) { + if (!isEffectiveExternalModule(sourceFile, compilerOptions)) { + diagnostics.add(createDiagnosticForNode(forInOrOfStatement.awaitModifier, + Diagnostics.for_await_loops_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)); + } + switch (moduleKind) { + case ModuleKind.Node16: + case ModuleKind.NodeNext: + if (sourceFile.impliedNodeFormat === ModuleKind.CommonJS) { + diagnostics.add( + createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.The_current_file_is_a_CommonJS_module_and_cannot_use_await_at_the_top_level) + ); + break; + } + // fallthrough + case ModuleKind.ES2022: + case ModuleKind.ESNext: + case ModuleKind.System: + if (languageVersion >= ScriptTarget.ES2017) { + break; + } + // fallthrough + default: + diagnostics.add( + createDiagnosticForNode(forInOrOfStatement.awaitModifier, + Diagnostics.Top_level_for_await_loops_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher + ) + ); + break; + } + } } - 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; + else { + // use of 'for-await-of' in non-async function + if (!hasParseDiagnostics(sourceFile)) { + const diagnostic = createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.for_await_loops_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules); + 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 (compilerOptions.jsxFragmentFactory) { - return parseIsolatedEntityName(compilerOptions.jsxFragmentFactory, languageVersion); + if (isForOfStatement(forInOrOfStatement) && !(forInOrOfStatement.flags & NodeFlags.AwaitContext) && + isIdentifier(forInOrOfStatement.initializer) && forInOrOfStatement.initializer.escapedText === "async") { + grammarErrorOnNode(forInOrOfStatement.initializer, Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_async); + return false; } - } - 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: Map; - if (resolvedTypeReferenceDirectives) { - // populate reverse mapping: file path -> type reference directive that was resolved to this file - fileToDirective = new Map(); - resolvedTypeReferenceDirectives.forEach(({ resolvedTypeReferenceDirective }, key, mode) => { - if (!resolvedTypeReferenceDirective?.resolvedFileName) { - return; - } - const file = host.getSourceFile(resolvedTypeReferenceDirective.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, mode); + if (forInOrOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList) { + const variableList = forInOrOfStatement.initializer as VariableDeclarationList; + 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; } - }); - } - return { - getReferencedExportContainer, - getReferencedImportDeclaration, - getReferencedDeclarationWithCollidingName, - isDeclarationWithCollidingName, - isValueAliasDeclaration: nodeIn => { - const node = getParseTreeNode(nodeIn); - // Synthesized nodes are always treated like values. - return node && canCollectSymbolAliasAccessabilityData ? isValueAliasDeclaration(node) : true; - }, - hasGlobalName, - isReferencedAliasDeclaration: (nodeIn, checkChildren?) => { - const node = getParseTreeNode(nodeIn); - // Synthesized nodes are always treated as referenced. - return node && canCollectSymbolAliasAccessabilityData ? 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: nodeIn => { - const node = getParseTreeNode(nodeIn, hasPossibleExternalModuleReference); - return node && getExternalModuleFileFromDeclaration(node); - }, - getTypeReferenceDirectivesForEntityName, - getTypeReferenceDirectivesForSymbol, - isLiteralConstDeclaration, - isLateBound: (nodeIn: Declaration): nodeIn is LateBoundDeclaration => { - const node = getParseTreeNode(nodeIn, isDeclaration); - const symbol = node && getSymbolOfDeclaration(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(getSymbolOfDeclaration(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 = getSymbolOfDeclaration(node); - if (!sym) { - return !node.locals ? [] : nodeBuilder.symbolTableToDeclarationStatements(node.locals, node, flags, tracker, bundled); + 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); } - return !sym.exports ? [] : nodeBuilder.symbolTableToDeclarationStatements(sym.exports, node, flags, tracker, bundled); - }, - isImportRequiredByAugmentation, - }; + const firstDeclaration = declarations[0]; - 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); - if (merged.declarations) { - for (const d of merged.declarations) { - const declFile = getSourceFileOfNode(d); - if (declFile === importTarget) { - return true; - } - } - } + 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 isInHeritageClause(node: PropertyAccessEntityNameExpression) { - return node.parent && node.parent.kind === SyntaxKind.ExpressionWithTypeArguments && node.parent.parent && node.parent.parent.kind === SyntaxKind.HeritageClause; + return false; + } + + function checkGrammarAccessor(accessor: AccessorDeclaration): boolean { + if (!(accessor.flags & NodeFlags.Ambient) && (accessor.parent.kind !== SyntaxKind.TypeLiteral) && (accessor.parent.kind !== SyntaxKind.InterfaceDeclaration)) { + if (languageVersion < ScriptTarget.ES5) { + return grammarErrorOnNode(accessor.name, Diagnostics.Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher); + } + if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(accessor.name)) { + return grammarErrorOnNode(accessor.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); + } + if (accessor.body === undefined && !hasSyntacticModifier(accessor, ModifierFlags.Abstract)) { + return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{"); + } + } + if (accessor.body) { + if (hasSyntacticModifier(accessor, ModifierFlags.Abstract)) { + return grammarErrorOnNode(accessor, Diagnostics.An_abstract_accessor_cannot_have_an_implementation); + } + if (accessor.parent.kind === SyntaxKind.TypeLiteral || accessor.parent.kind === SyntaxKind.InterfaceDeclaration) { + return grammarErrorOnNode(accessor.body, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts); + } + } + 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); } + } - // defined here to avoid outer scope pollution - function getTypeReferenceDirectivesForEntityName(node: EntityNameOrEntityNameExpression): [specifier: string, mode: ResolutionMode][] | undefined { - // program does not have any files with type reference directives - bail out - if (!fileToDirective) { - return undefined; - } - // computed property name should use node as value - // 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; - if (node.parent.kind === SyntaxKind.ComputedPropertyName) { - meaning = SymbolFlags.Value | SymbolFlags.ExportValue; + 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)); } - else { - meaning = SymbolFlags.Type | SymbolFlags.Namespace; - if ((node.kind === SyntaxKind.Identifier && isInTypeQuery(node)) || (node.kind === SyntaxKind.PropertyAccessExpression && !isInHeritageClause(node))) { - meaning = SymbolFlags.Value | SymbolFlags.ExportValue; + let parent = walkUpParenthesizedTypes(node.parent); + if (isInJSFile(parent) && isJSDocTypeExpression(parent)) { + const host = getJSDocHost(parent); + if (host) { + parent = getSingleVariableOfVariableStatement(host) || host; } } + 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 as VariableDeclaration).name, Diagnostics.A_variable_whose_type_is_a_unique_symbol_type_must_be_const); + } + break; - 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): [specifier: string, mode: ResolutionMode][] | undefined { - // program does not have any files with type reference directives - bail out - if (!fileToDirective || !isSymbolFromTypeDeclarationFile(symbol)) { - return undefined; - } - // check what declarations in the symbol can contribute to the target meaning - let typeReferenceDirectives: [specifier: string, mode: ResolutionMode][] | 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); + case SyntaxKind.PropertyDeclaration: + if (!isStatic(parent) || + !hasEffectiveReadonlyModifier(parent)) { + return grammarErrorOnNode((parent as PropertyDeclaration).name, Diagnostics.A_property_of_a_class_whose_type_is_a_unique_symbol_type_must_be_both_static_and_readonly); } - else { - // found at least one entry that does not originate from type reference directive - return undefined; + break; + + case SyntaxKind.PropertySignature: + if (!hasSyntacticModifier(parent, ModifierFlags.Readonly)) { + return grammarErrorOnNode((parent as PropertySignature).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); } - 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; + 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)); } + } + } - // 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; - } - } + function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) { + if (isNonBindableDynamicName(node)) { + return grammarErrorOnNode(node, message); + } + } - if (current.valueDeclaration && current.valueDeclaration.kind === SyntaxKind.SourceFile && current.flags & SymbolFlags.ValueModule) { - return false; - } + function checkGrammarMethod(node: MethodDeclaration | MethodSignature) { + if (checkGrammarFunctionLikeDeclaration(node)) { + return true; + } - // 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)) { + 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; } - } - return false; - } - - function addReferencedFilesToTypeDirective(file: SourceFile, key: string, mode: ResolutionMode) { - if (fileToDirective.has(file.path)) return; - fileToDirective.set(file.path, [key, mode]); - for (const { fileName, resolutionMode } of file.referencedFiles) { - const resolvedFile = resolveTripleslashReference(fileName, file.fileName); - const referencedFile = host.getSourceFile(resolvedFile); - if (referencedFile) { - addReferencedFilesToTypeDirective(referencedFile, key, resolutionMode || file.impliedNodeFormat); + 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; + } } - } - function getExternalModuleFileFromDeclaration(declaration: AnyImportOrReExport | ModuleDeclaration | ImportTypeNode | ImportCall): 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; + if (isClassLike(node.parent)) { + if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(node.name)) { + return grammarErrorOnNode(node.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); + } + // 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); + } } - return getDeclarationOfKind(moduleSymbol, SyntaxKind.SourceFile); - } - - // TSPLUS EXTENSION START - function isTsPlusImplicit(declaration: Declaration): declaration is VariableDeclaration { - if (isVariableDeclaration(declaration)) { - return declaration.isTsPlusImplicit; + 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); } - return false; - } - function collectTsPlusDeriveTags(declaration: Declaration) { - if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { - return declaration.tsPlusDeriveTags || []; + 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); } - return []; } - function collectTsPlusFluentTags(declaration: Declaration) { - if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { - return declaration.tsPlusFluentTags || []; + + function checkGrammarBreakOrContinueStatement(node: BreakOrContinueStatement): boolean { + let current: Node = node; + while (current) { + if (isFunctionLikeOrClassStaticBlockDeclaration(current)) { + return grammarErrorOnNode(node, Diagnostics.Jump_target_cannot_cross_function_boundary); + } + + switch (current.kind) { + case SyntaxKind.LabeledStatement: + if (node.label && (current as LabeledStatement).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 as LabeledStatement).statement, /*lookInLabeledStatements*/ 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, /*lookInLabeledStatements*/ false) && !node.label) { + // unlabeled break or continue within iteration statement - ok + return false; + } + break; + } + + current = current.parent; } - return []; - } - function collectTsPlusPipeableTags(declaration: Declaration) { - if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { - return declaration.tsPlusPipeableTags || []; + + 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); } - return []; - } - function collectTsPlusIndexTags(declaration: Declaration) { - if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { - return declaration.tsPlusIndexTags || []; + 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); } - return []; } - function collectTsPlusPipeableIndexTags(declaration: Declaration) { - if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { - return declaration.tsPlusPipeableIndexTags || []; + + 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); + } } - return []; - } - function collectTsPlusTypeTags(declaration: Declaration) { - if (isInterfaceDeclaration(declaration) || isTypeAliasDeclaration(declaration) || isClassDeclaration(declaration)) { - return declaration.tsPlusTypeTags || []; + + 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); } - return []; } - function collectTsPlusCompanionTags(declaration: Declaration) { - if (isClassDeclaration(declaration) || isInterfaceDeclaration(declaration) || isTypeAliasDeclaration(declaration)) { - return declaration.tsPlusCompanionTags || []; - } - return []; + + function isStringOrNumberLiteralExpression(expr: Expression) { + return isStringOrNumericLiteralLike(expr) || + expr.kind === SyntaxKind.PrefixUnaryExpression && (expr as PrefixUnaryExpression).operator === SyntaxKind.MinusToken && + (expr as PrefixUnaryExpression).operand.kind === SyntaxKind.NumericLiteral; } - function collectTsPlusMacroTags(declaration: Declaration) { - if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration) || isCallSignatureDeclaration(declaration)) { - return declaration.tsPlusMacroTags || []; - } - return []; + + function isBigIntLiteralExpression(expr: Expression) { + return expr.kind === SyntaxKind.BigIntLiteral || + expr.kind === SyntaxKind.PrefixUnaryExpression && (expr as PrefixUnaryExpression).operator === SyntaxKind.MinusToken && + (expr as PrefixUnaryExpression).operand.kind === SyntaxKind.BigIntLiteral; } - function collectTsPlusUnifyTags(declaration: Declaration) { - if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { - return declaration.tsPlusUnifyTags || []; + + function isSimpleLiteralEnumReference(expr: Expression) { + if ((isPropertyAccessExpression(expr) || (isElementAccessExpression(expr) && isStringOrNumberLiteralExpression(expr.argumentExpression))) && + isEntityNameExpression(expr.expression)) { + return !!(checkExpressionCached(expr).flags & TypeFlags.EnumLike); } - return []; } - function collectTsPlusStaticTags(declaration: Node) { - if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration) || isClassDeclaration(declaration)) { - return declaration.tsPlusStaticTags || []; + + function checkAmbientInitializer(node: VariableDeclaration | PropertyDeclaration | PropertySignature) { + const initializer = node.initializer; + 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); + } } - return []; } - function collectTsPlusOperatorTags(declaration: Node) { - if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { - return declaration.tsPlusOperatorTags || []; + + 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); + } + } } - return []; - } - function collectTsPlusPipeableOperatorTags(declaration: Node) { - if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { - return declaration.tsPlusPipeableOperatorTags || []; + + 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 + : !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); } - return []; - } - function collectTsPlusGetterTags(declaration: Node) { - if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { - return declaration.tsPlusGetterTags || []; + + if ((moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && moduleKind !== ModuleKind.System && + !(node.parent.parent.flags & NodeFlags.Ambient) && hasSyntacticModifier(node.parent.parent, ModifierFlags.Export)) { + checkESModuleMarker(node.name); } - return []; - } - function createTsPlusSignature(call: Signature, exportName: string, file: SourceFile): TsPlusSignature { - const signature = cloneSignature(call) as TsPlusSignature - signature.tsPlusTag = "TsPlusSignature"; - signature.tsPlusFile = file; - signature.tsPlusExportName = exportName; - signature.tsPlusOriginal = call; - return signature + + 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 thisifyTsPlusSignature(declaration: FunctionDeclaration | VariableDeclaration, call: Signature, exportName: string, file: SourceFile, reportDiagnostic: (diagnostic: DiagnosticMessage) => void) { - const signature = createTsPlusSignature(call, exportName, file); - if (isSelfARestParameter(signature)) { - reportDiagnostic(Diagnostics.The_first_parameter_of_a_fluent_function_cannot_be_a_rest_parameter); - return undefined; + + 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); + } } - const target = signature.parameters[0]; - signature.thisParameter = createSymbolWithType(createSymbol(target.flags, "this" as __String), getTypeOfSymbol(target)); - const typePredicate = getTypePredicateOfSignature(call); - if (typePredicate && typePredicate.parameterIndex === 0) { - signature.resolvedTypePredicate = createTypePredicate( - typePredicate.kind === TypePredicateKind.Identifier ? TypePredicateKind.This : TypePredicateKind.AssertsThis, - "this", - 0, - typePredicate.type - ); + else { + const elements = name.elements; + for (const element of elements) { + if (!isOmittedExpression(element)) { + return checkESModuleMarker(element.name); + } + } } - signature.parameters = signature.parameters.slice(1, signature.parameters.length); - signature.minArgumentCount = signature.minArgumentCount - 1; - signature.tsPlusDeclaration = declaration; - signature.resolvedReturnType = call.resolvedReturnType; - return signature; - } - function createTsPlusFluentSymbol(name: string, signatures: TsPlusSignature[]): TsPlusFluentSymbol { - const symbol = createSymbol(SymbolFlags.Function, name as __String) as TsPlusFluentSymbol; - symbol.tsPlusTag = TsPlusSymbolTag.Fluent; - symbol.tsPlusResolvedSignatures = signatures; - symbol.tsPlusName = name; - return symbol; - } - function createTsPlusFluentSymbolWithType(name: string, allSignatures: TsPlusSignature[]): TransientSymbol { - const symbol = createTsPlusFluentSymbol(name, allSignatures); - const type = createAnonymousType(symbol, emptySymbols, allSignatures, [], []); - return createSymbolWithType(symbol, type); - } - function createTsPlusGetterVariableSymbol(name: string, dataFirst: VariableDeclaration & { name: Identifier }, returnType: Type, selfType: Type) { - const symbol = createSymbol(SymbolFlags.Property, name as __String) as TsPlusGetterVariableSymbol; - getSymbolLinks(symbol).type = returnType; - symbol.tsPlusTag = TsPlusSymbolTag.GetterVariable; - symbol.tsPlusDeclaration = dataFirst; - symbol.tsPlusSelfType = selfType; - symbol.tsPlusName = name; - return symbol; - } - function createTsPlusStaticFunctionSymbol(name: string, declaration: FunctionDeclaration | VariableDeclaration, signatures: Signature[]): TsPlusStaticFunctionSymbol { - const symbol = createSymbol(SymbolFlags.Function, name as __String) as TsPlusStaticFunctionSymbol; - symbol.tsPlusTag = TsPlusSymbolTag.StaticFunction; - symbol.tsPlusDeclaration = declaration; - symbol.tsPlusResolvedSignatures = signatures; - symbol.tsPlusName = name; - return symbol; - } - function createTsPlusPipeableDeclarationSymbol(name: string, declaration: FunctionDeclaration | VariableDeclarationWithFunction | VariableDeclarationWithFunctionType) { - const symbol = createSymbol(SymbolFlags.Function, name as __String) as TsPlusPipeableDeclarationSymbol; - symbol.tsPlusTag = TsPlusSymbolTag.PipeableDeclaration; - symbol.tsPlusDeclaration = declaration; - return symbol; + return false; } - function createTsPlusStaticValueSymbol(name: string, declaration: VariableDeclaration | ClassDeclaration, originalSymbol: Symbol): Symbol { - const symbol = cloneSymbol(originalSymbol) as TsPlusStaticValueSymbol; - symbol.tsPlusTag = TsPlusSymbolTag.StaticValue; - symbol.tsPlusResolvedSignatures = []; - symbol.tsPlusName = name; - symbol.tsPlusDeclaration = declaration as (VariableDeclaration | ClassDeclaration) & { name: Identifier }; - symbol.escapedName = name as __String; - let patchedDeclaration: VariableDeclaration | ClassDeclaration - if (isVariableDeclaration(declaration)) { - patchedDeclaration = factory.updateVariableDeclaration( - declaration, - factory.createIdentifier(name), - declaration.exclamationToken, - declaration.type, - declaration.initializer - ); + + function checkGrammarNameInLetOrConstDeclarations(name: Identifier | BindingPattern): boolean { + if (name.kind === SyntaxKind.Identifier) { + if (name.escapedText === "let") { + return grammarErrorOnNode(name, Diagnostics.let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations); + } } else { - patchedDeclaration = factory.updateClassDeclaration( - declaration, - declaration.modifiers, - factory.createIdentifier(name), - declaration.typeParameters, - declaration.heritageClauses, - declaration.members - ) + const elements = name.elements; + for (const element of elements) { + if (!isOmittedExpression(element)) { + checkGrammarNameInLetOrConstDeclarations(element.name); + } + } } - setParent(patchedDeclaration, declaration.parent); - setParent(patchedDeclaration.name!, patchedDeclaration) - patchedDeclaration.name!.tsPlusName = name; - symbol.declarations = [patchedDeclaration]; - patchedDeclaration.jsDoc = getJSDocCommentsAndTags(declaration) as JSDoc[]; - - const originalSymbolLinks = getSymbolLinks(originalSymbol); - const symbolLinks = getSymbolLinks(symbol); - symbolLinks.uniqueESSymbolType = originalSymbolLinks.uniqueESSymbolType; - symbolLinks.declaredType = originalSymbolLinks.declaredType; - - return symbol; - } - function createTsPlusGetterFunctionSymbol(name: string, dataFirst: FunctionDeclaration, returnType: Type, selfType: Type) { - const symbol = createSymbol(SymbolFlags.Property, name as __String) as TsPlusGetterSymbol; - getSymbolLinks(symbol).type = returnType; - symbol.tsPlusTag = TsPlusSymbolTag.Getter; - symbol.tsPlusDeclaration = dataFirst; - symbol.tsPlusSelfType = selfType; - symbol.tsPlusName = name; - return symbol; + return false; } - function getTsPlusFluentSignaturesForFunctionDeclaration(file: SourceFile, exportName: string, dataFirst: FunctionDeclaration) { - function reportDiagnostic(message: DiagnosticMessage) { - error(dataFirst, message); + + function checkGrammarVariableDeclarationList(declarationList: VariableDeclarationList): boolean { + const declarations = declarationList.declarations; + if (checkGrammarForDisallowedTrailingComma(declarationList.declarations)) { + return true; } - const type = getTypeOfNode(dataFirst); - const signatures = getSignaturesOfType(type, SignatureKind.Call); - const thisifiedSignatures = signatures.map((signature) => thisifyTsPlusSignature(dataFirst, signature, exportName, file, reportDiagnostic)) - if (!isEachDefined(thisifiedSignatures)) { - return; + + if (!declarationList.declarations.length) { + return grammarErrorAtPos(declarationList, declarations.pos, declarations.end - declarations.pos, Diagnostics.Variable_declaration_list_cannot_be_empty); } - const thisifiedType = createAnonymousType(undefined, emptySymbols, thisifiedSignatures as TsPlusSignature[], [], []) - return [thisifiedType, thisifiedSignatures] as const; + return false; } - function getTsPlusFluentSignaturesForVariableDeclaration(file: SourceFile, exportName: string, declaration: VariableDeclaration & { name: Identifier }) { - function reportDiagnostic(message: DiagnosticMessage) { - error(declaration, message); + + 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); } - const type = getTypeOfNode(declaration); - const signatures = getSignaturesOfType(type, SignatureKind.Call); - if(signatures.every((sigSymbol) => sigSymbol.parameters.every((paramSymbol) => paramSymbol.valueDeclaration && isVariableLike(paramSymbol.valueDeclaration) && isParameterDeclaration(paramSymbol.valueDeclaration)))) { - let thisifiedSignatures = signatures.map((signature) => thisifyTsPlusSignature(declaration, signature, exportName, file, reportDiagnostic)) - if (!isEachDefined(thisifiedSignatures)) { - return; + + 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); } - const thisifiedType = createAnonymousType(undefined, emptySymbols, thisifiedSignatures as TsPlusSignature[], [], []); - return [thisifiedType, thisifiedSignatures] as const; } } - function getTsPlusGetterSymbolForFunctionDeclaration(_name: string, _dataFirst: FunctionDeclaration) { - return (selfNode: Expression) => { - const res = checkTsPlusCustomCall( - _dataFirst, - selfNode, - [selfNode], - CheckMode.Normal - ); - if (isErrorType(res)) { - return void 0; - } - return createTsPlusGetterFunctionSymbol(_name, _dataFirst, res, getTypeOfNode(selfNode)); - }; + + 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, unescapeLeadingUnderscores(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, unescapeLeadingUnderscores(node.name.escapedText), tokenToString(node.keywordToken), "meta"); + } + break; + } } - function getTsPlusGetterSymbolForVariableDeclaration(name: string, declaration: VariableDeclaration & { name: Identifier }) { - return (selfNode: Expression) => { - const res = checkTsPlusCustomCall( - declaration, - selfNode, - [selfNode], - CheckMode.Normal - ); - if (isErrorType(res)) { - return void 0; - } - return createTsPlusGetterVariableSymbol(name, declaration, res, getTypeOfNode(selfNode)); - }; + + function hasParseDiagnostics(sourceFile: SourceFile): boolean { + return sourceFile.parseDiagnostics.length > 0; } - function getTsPlusStaticSymbolForFunctionDeclaration(file: SourceFile, exportName: string, name: string, dataFirst: FunctionDeclaration | VariableDeclarationWithFunction) { - const signatures = getSignaturesOfType(getTypeOfNode(dataFirst), SignatureKind.Call); - const methods = map(signatures, (s) => createTsPlusSignature(s, exportName, file)); - const symbol = createTsPlusStaticFunctionSymbol(name, dataFirst, methods); - const final = createAnonymousType(symbol, emptySymbols, methods, [], []); - return [final, createSymbolWithType(symbol, final)] as const; + + function grammarErrorOnFirstToken(node: Node, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message, ...args)); + return true; + } + return false; } - function augmentPipeableIdentifierSymbol(identifier: Identifier, target: string, _exportName: string, name: string, dataFirstType: () => Type, declaration: FunctionDeclaration | VariableDeclarationWithFunction) { - const identifierType = getTypeOfNode(identifier); - if (identifierType.symbol) { - (identifierType.symbol as TsPlusPipeableIdentifierSymbol).tsPlusTag = TsPlusSymbolTag.PipeableIdentifier; - (identifierType.symbol as TsPlusPipeableIdentifierSymbol).tsPlusDeclaration = declaration; - (identifierType.symbol as TsPlusPipeableIdentifierSymbol).tsPlusTypeName = target; - (identifierType.symbol as TsPlusPipeableIdentifierSymbol).tsPlusName = name; - (identifierType.symbol as TsPlusPipeableIdentifierSymbol).getTsPlusDataFirstType = dataFirstType; + + function grammarErrorAtPos(nodeForSourceFile: Node, start: number, length: number, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { + const sourceFile = getSourceFileOfNode(nodeForSourceFile); + if (!hasParseDiagnostics(sourceFile)) { + diagnostics.add(createFileDiagnostic(sourceFile, start, length, message, ...args)); + return true; } + return false; } - function isPipeableSelfARestParameter(signature: Signature): boolean { - return getSignaturesOfType(getReturnTypeOfSignature(signature), SignatureKind.Call).find(isSelfARestParameter) != null; + + function grammarErrorOnNodeSkippedOn(key: keyof CompilerOptions, node: Node, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + errorSkippedOn(key, node, message, ...args); + return true; + } + return false; } - function getTsPlusFluentSignatureForPipeableFunction(file: SourceFile, exportName: string, name: string, pipeable: FunctionDeclaration, thisify = false): [Type, TsPlusSignature[]] | undefined { - function reportDiagnostic(diagnostic: DiagnosticMessage) { - error(pipeable, diagnostic); + + function grammarErrorOnNode(node: Node, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + diagnostics.add(createDiagnosticForNode(node, message, ...args)); + return true; } - const type = getTypeOfNode(pipeable); - const signatures = getSignaturesOfType(type, SignatureKind.Call); - if (pipeable.body) { - const returnStatement = find( - pipeable.body.statements, - (s): s is ReturnStatement & { expression: ArrowFunction } => - isReturnStatement(s) && !!s.expression && isArrowFunction(s.expression) - ); - if (returnStatement && returnStatement.expression.parameters.length === 1) { - if (signatures.find(isPipeableSelfARestParameter)) { - error(pipeable, Diagnostics.The_first_parameter_of_a_pipeable_annotated_function_cannot_be_a_rest_parameter); - return; - } - const tsPlusSignatures = flatMap(signatures, (sig) => { - const returnType = getReturnTypeOfSignature(sig); - const returnSignatures = getSignaturesOfType(returnType, SignatureKind.Call); - return flatMap(returnSignatures, (rsig) => { - let newSig = createTsPlusSignature(sig, exportName, file); - newSig.parameters = [...rsig.parameters, ...sig.parameters]; - newSig.typeParameters = [...(rsig.typeParameters ?? []), ...(sig.typeParameters ?? [])]; - newSig.resolvedReturnType = getReturnTypeOfSignature(rsig); - newSig.minArgumentCount = newSig.minArgumentCount + 1; - const newDecl = factory.updateFunctionDeclaration( - pipeable, - pipeable.modifiers, - pipeable.asteriskToken, - pipeable.name, - [...(returnStatement.expression.typeParameters ?? []), ...(pipeable.typeParameters ?? [])], - [...returnStatement.expression.parameters, ...pipeable.parameters], - returnStatement.expression.type, - undefined - ); + return false; + } - newDecl.jsDoc = pipeable.jsDoc; - newDecl.symbol = createTsPlusPipeableDeclarationSymbol(name, pipeable); - setParent(newDecl, pipeable.parent); - setOriginalNode(newDecl, pipeable) + 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); + } + } - newSig.declaration = newDecl; - newSig.tsPlusDeclaration = pipeable; + function checkGrammarConstructorTypeAnnotation(node: ConstructorDeclaration) { + const type = node.type || getEffectiveReturnTypeNode(node); + if (type) { + return grammarErrorOnNode(type, Diagnostics.Type_annotation_cannot_appear_on_a_constructor_declaration); + } + } - if (thisify) { - const thisified = thisifyTsPlusSignature(pipeable, newSig, exportName, file, reportDiagnostic); - if (!thisified) { - return; - } - newSig = thisified; - } - newSig.tsPlusPipeable = true; - return newSig; - }); - }) as TsPlusSignature[]; - const dataFirstType = createAnonymousType(undefined, emptySymbols, tsPlusSignatures, [], []); - return [dataFirstType, tsPlusSignatures]; - } + function checkGrammarProperty(node: PropertyDeclaration | PropertySignature) { + if (isComputedPropertyName(node.name) && isBinaryExpression(node.name.expression) && node.name.expression.operatorToken.kind === SyntaxKind.InKeyword) { + return grammarErrorOnNode(node.parent.members[0], Diagnostics.A_mapped_type_may_not_declare_properties_or_methods); } - const filteredSignatures = filter(signatures, (sig) => { - if (!sig.declaration || !sig.declaration.type) { - return false + 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 (isFunctionTypeNode(sig.declaration.type) && sig.declaration.type.parameters.length === 1) { - return true + if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_class_property_declaration_must_have_a_simple_literal_type_or_a_unique_symbol_type)) { + return true; } - if (isCallSignatureDeclaration(sig.declaration) && sig.declaration.parameters.length === 1) { - 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); + } + if (languageVersion < ScriptTarget.ES2015 && isAutoAccessorPropertyDeclaration(node)) { + return grammarErrorOnNode(node.name, Diagnostics.Properties_with_the_accessor_modifier_are_only_available_when_targeting_ECMAScript_2015_and_higher); + } + if (isAutoAccessorPropertyDeclaration(node) && checkGrammarForInvalidQuestionMark(node.questionToken, Diagnostics.An_accessor_property_cannot_be_declared_optional)) { + return true; } - return false - }) - if (filteredSignatures.find(isPipeableSelfARestParameter)) { - error(pipeable, Diagnostics.The_first_parameter_of_a_pipeable_annotated_function_cannot_be_a_rest_parameter); - return; } - if (filteredSignatures.length > 0) { - const tsPlusSignatures = flatMap(filteredSignatures, (sig) => { - const returnType = getReturnTypeOfSignature(sig); - const returnSignatures = getSignaturesOfType(returnType, SignatureKind.Call); - return flatMap(returnSignatures, (rsig) => { - let newSig = createTsPlusSignature(sig, exportName, file); - newSig.parameters = [...rsig.parameters, ...sig.parameters]; - newSig.typeParameters = [...(rsig.typeParameters ?? []), ...(sig.typeParameters ?? [])]; - newSig.resolvedReturnType = getReturnTypeOfSignature(rsig); - newSig.minArgumentCount = newSig.minArgumentCount + 1; - const newDecl = factory.updateFunctionDeclaration( - pipeable, - pipeable.modifiers, - pipeable.asteriskToken, - pipeable.name, - [...(rsig.declaration?.typeParameters as NodeArray ?? []), ...(sig.declaration?.typeParameters as NodeArray ?? [])], - [...(rsig.declaration?.parameters as NodeArray ?? []), ...(sig.declaration?.parameters as NodeArray ?? [])], - rsig.declaration?.type as TypeNode, - undefined - ); + 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; + } - newDecl.jsDoc = pipeable.jsDoc; - newDecl.symbol = createTsPlusPipeableDeclarationSymbol(name, pipeable); - setParent(newDecl, pipeable.parent); - setOriginalNode(newDecl, pipeable); + // Interfaces cannot contain property declarations + Debug.assertNode(node, isPropertySignature); + if (node.initializer) { + return grammarErrorOnNode(node.initializer, Diagnostics.An_interface_property_cannot_have_an_initializer); + } + } + else if (isTypeLiteralNode(node.parent)) { + 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; + } + // Type literals cannot contain property declarations + Debug.assertNode(node, isPropertySignature); + if (node.initializer) { + return grammarErrorOnNode(node.initializer, Diagnostics.A_type_literal_property_cannot_have_an_initializer); + } + } - newSig.declaration = newDecl; - newSig.tsPlusDeclaration = pipeable; + if (node.flags & NodeFlags.Ambient) { + checkAmbientInitializer(node); + } - if (thisify) { - const thisifiedSignature = thisifyTsPlusSignature(pipeable, newSig, exportName, file, reportDiagnostic); - if (!thisifiedSignature) { - return; - } - newSig = thisifiedSignature; - } - newSig.tsPlusPipeable = true; - return newSig; - }); - }) as TsPlusSignature[]; - const dataFirstType = createAnonymousType(undefined, emptySymbols, tsPlusSignatures, [], []); - return [dataFirstType, tsPlusSignatures]; + if (isPropertyDeclaration(node) && node.exclamationToken && (!isClassLike(node.parent) || !node.type || node.initializer || + node.flags & NodeFlags.Ambient || isStatic(node) || hasAbstractModifier(node))) { + 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); } - return undefined; } - function isVariableDeclarationWithFunction(node: VariableDeclaration): node is VariableDeclarationWithFunction { - return isIdentifier(node.name) && !!node.initializer && (isArrowFunction(node.initializer) || isFunctionExpression(node.initializer)); + + 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 isEachDefined(array: (A | undefined)[]): array is A[] { - for (const a of array) { - if (a === undefined) { - return false; + + function checkGrammarTopLevelElementsForRequiredDeclareModifier(file: SourceFile): boolean { + for (const decl of file.statements) { + if (isDeclaration(decl) || decl.kind === SyntaxKind.VariableStatement) { + if (checkGrammarTopLevelElementForRequiredDeclareModifier(decl)) { + return true; + } } } - return true; + return false; } - function getTsPlusFluentSignatureForPipeableVariableDeclaration(file: SourceFile, exportName: string, name: string, pipeable: VariableDeclarationWithFunction | VariableDeclarationWithFunctionType, thisify = false): [Type, TsPlusSignature[]] | undefined { - function reportDiagnostic(diagnostic: DiagnosticMessage) { - error(pipeable, diagnostic); - } - const type = getTypeOfNode(pipeable); - const signatures = getSignaturesOfType(type, SignatureKind.Call); - if (isVariableDeclarationWithFunction(pipeable)) { - const initializer = pipeable.initializer; - const body = initializer.body; - let returnFn: ArrowFunction | FunctionTypeNode | undefined; - if (isBlock(body)) { - const candidate = find( - body.statements, - (s): s is ReturnStatement & { expression: ArrowFunction } => - isReturnStatement(s) && !!s.expression && isArrowFunction(s.expression) - ); - if (candidate) { - returnFn = candidate.expression; + + 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 if (isArrowFunction(body)) { - returnFn = body; + 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)); } - if (returnFn && returnFn.parameters.length === 1) { - if (signatures.find(isPipeableSelfARestParameter)) { - error(pipeable, Diagnostics.The_first_parameter_of_a_pipeable_annotated_function_cannot_be_a_rest_parameter); - return; - } - const tsPlusSignatures = flatMap(signatures, (sig) => { - const returnType = getReturnTypeOfSignature(sig); - const returnSignatures = getSignaturesOfType(returnType, SignatureKind.Call); - return flatMap(returnSignatures, (rsig) => { - let newSig = createTsPlusSignature(sig, exportName, file); - newSig.parameters = [...rsig.parameters, ...sig.parameters]; - newSig.typeParameters = [...(rsig.typeParameters ?? []), ...(sig.typeParameters ?? [])]; - newSig.resolvedReturnType = getReturnTypeOfSignature(rsig); - newSig.minArgumentCount = newSig.minArgumentCount + 1; - let newDecl: ArrowFunction | FunctionExpression | FunctionTypeNode; - if (isArrowFunction(initializer)) { - newDecl = factory.updateArrowFunction( - initializer, - initializer.modifiers, - [...(returnFn!.typeParameters ?? []), ...(initializer.typeParameters ?? [])], - [...returnFn!.parameters, ...initializer.parameters], - returnFn!.type, - initializer.equalsGreaterThanToken, - factory.createBlock([]) - ); - } - else { - newDecl = factory.updateFunctionExpression( - initializer, - initializer.modifiers, - initializer.asteriskToken, - initializer.name, - [...(returnFn!.typeParameters ?? []), ...(initializer.typeParameters ?? [])], - [...returnFn!.parameters, ...initializer.parameters], - returnFn!.type, - factory.createBlock([]) - ); - } - - newDecl.jsDoc = pipeable.jsDoc; - newDecl.symbol = createTsPlusPipeableDeclarationSymbol(name, pipeable); - setParent(newDecl, pipeable.parent); - setOriginalNode(newDecl, pipeable); + } + return false; + } - newSig.declaration = newDecl; - newSig.tsPlusDeclaration = pipeable; + function checkGrammarNumericLiteral(node: NumericLiteral) { + // Realism (size) checking + // We should test against `getTextOfNode(node)` rather than `node.text`, because `node.text` for large numeric literals can contain "." + // e.g. `node.text` for numeric literal `1100000000000000000000` is `1.1e21`. + const isFractional = getTextOfNode(node).indexOf(".") !== -1; + const isScientific = node.numericLiteralFlags & TokenFlags.Scientific; - if (thisify) { - const thisifiedSignature = thisifyTsPlusSignature(pipeable, newSig, exportName, file, reportDiagnostic); - if (!thisifiedSignature) { - return; - } - newSig = thisifiedSignature; - } - newSig.tsPlusPipeable = true; - return newSig; - }); - }) as TsPlusSignature[]; - const dataFirstType = createAnonymousType(type.symbol, emptySymbols, tsPlusSignatures, [], []); - return [dataFirstType, tsPlusSignatures]; - } + // Scientific notation (e.g. 2e54 and 1e00000000010) can't be converted to bigint + // Fractional numbers (e.g. 9000000000000000.001) are inherently imprecise anyway + if (isFractional || isScientific) { + return; } - const filteredSignatures = filter(signatures, (sig) => { - if (!sig.declaration || !sig.declaration.type) { - return false - } - if (isFunctionTypeNode(sig.declaration.type) && sig.declaration.type.parameters.length === 1) { - return true - } - if (isCallSignatureDeclaration(sig.declaration) && sig.declaration.parameters.length === 1) { - return true - } - return false - }) - if (filteredSignatures.find(isPipeableSelfARestParameter)) { - error(pipeable, Diagnostics.The_first_parameter_of_a_pipeable_annotated_function_cannot_be_a_rest_parameter); + + // Here `node` is guaranteed to be a numeric literal representing an integer. + // We need to judge whether the integer `node` represents is <= 2 ** 53 - 1, which can be accomplished by comparing to `value` defined below because: + // 1) when `node` represents an integer <= 2 ** 53 - 1, `node.text` is its exact string representation and thus `value` precisely represents the integer. + // 2) otherwise, although `node.text` may be imprecise string representation, its mathematical value and consequently `value` cannot be less than 2 ** 53, + // thus the result of the predicate won't be affected. + const value = +node.text; + if (value <= 2 ** 53 - 1) { return; } - if (filteredSignatures.length > 0) { - const tsPlusSignatures = flatMap(filteredSignatures, (sig) => { - const returnFn = sig.declaration!.type! as FunctionTypeNode - const returnType = getReturnTypeOfSignature(sig); - const returnSignatures = getSignaturesOfType(returnType, SignatureKind.Call); - return flatMap(returnSignatures, (rsig) => { - let newSig = createTsPlusSignature(sig, exportName, file); - newSig.parameters = [...rsig.parameters, ...sig.parameters]; - newSig.typeParameters = [...(rsig.typeParameters ?? []), ...(sig.typeParameters ?? [])]; - newSig.resolvedReturnType = getReturnTypeOfSignature(rsig); - newSig.minArgumentCount = newSig.minArgumentCount + 1; - const newDecl = factory.createFunctionTypeNode( - factory.createNodeArray([...(returnFn.typeParameters ?? []), ...(sig.declaration?.typeParameters as NodeArray ?? [])]), - factory.createNodeArray([...returnFn.parameters, ...(sig.declaration?.parameters as NodeArray ?? [])]), - returnFn.type - ); - newDecl.jsDoc = pipeable.parent.parent.jsDoc; - newDecl.symbol = createTsPlusPipeableDeclarationSymbol(name, pipeable); - setParent(newDecl, pipeable.parent); - setOriginalNode(newDecl, pipeable); + 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)); + } - newSig.declaration = newDecl; - newSig.tsPlusDeclaration = pipeable; + 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; + } - if (thisify) { - const thisifiedSignature = thisifyTsPlusSignature(pipeable, newSig, exportName, file, reportDiagnostic); - if (!thisifiedSignature) { - return; - } - newSig = thisifiedSignature; - } - newSig.tsPlusPipeable = true; - return newSig; - }); - }) as TsPlusSignature[]; - const dataFirstType = createAnonymousType(type.symbol, emptySymbols, tsPlusSignatures, [], []); - return [dataFirstType, tsPlusSignatures]; + function grammarErrorAfterFirstToken(node: Node, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { + const sourceFile = getSourceFileOfNode(node); + if (!hasParseDiagnostics(sourceFile)) { + const span = getSpanOfTokenAtPosition(sourceFile, node.pos); + diagnostics.add(createFileDiagnostic(sourceFile, textSpanEnd(span), /*length*/ 0, message, ...args)); + return true; } - return undefined; + return false; } - function isSelfARestParameter(signature: Signature): boolean { - if ( - signature.parameters[0] && - signature.parameters[0].declarations && - signature.parameters[0].declarations.find((decl) => isVariableLike(decl) && isParameterDeclaration(decl) && isRestParameter(decl as ParameterDeclaration)) - ) { - return true + + 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); + } + if (node.isTypeOnly && node.namedBindings?.kind === SyntaxKind.NamedImports) { + return checkGrammarNamedImportsOrExports(node.namedBindings); } return false; } - function addToTypeSymbolCache(symbol: Symbol, tag: string, priority: "before" | "after") { - if (!typeSymbolCache.has(symbol)) { - typeSymbolCache.set(symbol, []); + + function checkGrammarNamedImportsOrExports(namedBindings: NamedImportsOrExports): boolean { + return !!forEach(namedBindings.elements, specifier => { + if (specifier.isTypeOnly) { + return grammarErrorOnFirstToken( + specifier, + specifier.kind === SyntaxKind.ImportSpecifier + ? Diagnostics.The_type_modifier_cannot_be_used_on_a_named_import_when_import_type_is_used_on_its_import_statement + : Diagnostics.The_type_modifier_cannot_be_used_on_a_named_export_when_export_type_is_used_on_its_export_statement); + } + }); + } + + function checkGrammarImportCallExpression(node: ImportCall): boolean { + if (compilerOptions.verbatimModuleSyntax && moduleKind === ModuleKind.CommonJS) { + return grammarErrorOnNode(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); } - const tags = typeSymbolCache.get(symbol)! - if (!tags.includes(tag)) { - if (priority === "before") { - typeSymbolCache.set(symbol, [tag, ...tags]) - } else { - tags.push(tag) + + if (moduleKind === ModuleKind.ES2015) { + return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_es2020_es2022_esnext_commonjs_amd_system_umd_node16_or_nodenext); + } + + if (node.typeArguments) { + return grammarErrorOnNode(node, Diagnostics.This_use_of_import_is_invalid_import_calls_can_be_written_but_they_must_have_parentheses_and_cannot_have_type_arguments); + } + + const nodeArguments = node.arguments; + if (moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.NodeNext && moduleKind !== ModuleKind.Node16) { + // We are allowed trailing comma after proposal-import-assertions. + checkGrammarForDisallowedTrailingComma(nodeArguments); + + if (nodeArguments.length > 1) { + const assertionArgument = nodeArguments[1]; + return grammarErrorOnNode(assertionArgument, Diagnostics.Dynamic_imports_only_support_a_second_argument_when_the_module_option_is_set_to_esnext_node16_or_nodenext); } } - } - function addToCompanionSymbolCache(symbol: Symbol, tag: string) { - if (!companionSymbolCache.has(symbol)) { - companionSymbolCache.set(symbol, []) + + if (nodeArguments.length === 0 || nodeArguments.length > 2) { + return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_can_only_accept_a_module_specifier_and_an_optional_assertion_as_arguments); } - const tags = companionSymbolCache.get(symbol)! - if (!tags.includes(tag)) { - tags.push(tag) + + // 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. + const spreadElement = find(nodeArguments, isSpreadElement); + if (spreadElement) { + return grammarErrorOnNode(spreadElement, Diagnostics.Argument_of_dynamic_import_cannot_be_spread_element); } + return false; } - function tryCacheTsPlusGlobalSymbol(declaration: ImportDeclaration): void { - if (declaration.isTsPlusGlobal) { - (declaration.importClause!.namedBindings as NamedImports).elements.forEach((importSpecifier) => { - tsPlusGlobalImportCache.set(importSpecifier.name.escapedText as string, { declaration, importSpecifier, moduleSpecifier: declaration.moduleSpecifier as StringLiteral }); - }) + + 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 cacheTsPlusType(declaration: InterfaceDeclaration | TypeAliasDeclaration | ClassDeclaration): void { - unresolvedTypeDeclarations.add(declaration); + + function findBestTypeForObjectLiteral(source: Type, unionTarget: UnionOrIntersectionType) { + if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && someType(unionTarget, isArrayLikeType)) { + return find(unionTarget.types, t => !isArrayLikeType(t)); + } } - function tryCacheTsPlusInheritance(typeSymbol: Symbol, heritage: readonly HeritageClause[]): void { - if (!inheritanceSymbolCache.has(typeSymbol)) { - inheritanceSymbolCache.set(typeSymbol, new Set()); + + 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); } + } - const heritageExtensions = inheritanceSymbolCache.get(typeSymbol)!; - forEach(heritage, (clause) => { - forEach(clause.types, (node) => { - if (isIdentifier(node.expression)) { - const nodeType = getTypeOfNode(node.expression); - if (nodeType.flags & TypeFlags.Intersection) { - forEach((nodeType as IntersectionType).types, (type) => { - if (type.symbol) { - heritageExtensions.add(type.symbol); - } - if (type.aliasSymbol) { - heritageExtensions.add(type.aliasSymbol); - } - }) - } - if (nodeType.symbol) { - heritageExtensions.add(nodeType.symbol); - } - if (nodeType.aliasSymbol) { - heritageExtensions.add(nodeType.aliasSymbol) + function findMostOverlappyType(source: Type, unionTarget: UnionOrIntersectionType) { + let bestMatch: Type | undefined; + if (!(source.flags & (TypeFlags.Primitive | TypeFlags.InstantiablePrimitive))) { + let matchingCount = 0; + for (const target of unionTarget.types) { + if (!(target.flags & (TypeFlags.Primitive | TypeFlags.InstantiablePrimitive))) { + const overlap = getIntersectionType([getIndexType(source), getIndexType(target)]); + if (overlap.flags & TypeFlags.Index) { + // perfect overlap of keys + return target; } - } else if (isCallExpression(node.expression)) { - const resolvedSignature = getResolvedSignature(node.expression); - const returnType = getReturnTypeOfSignature(resolvedSignature); - if (returnType) { - if (returnType.symbol) { - heritageExtensions.add(returnType.symbol); - } - if (returnType.flags & TypeFlags.Intersection) { - forEach((returnType as IntersectionType).types, (type) => { - if (type.symbol) { - heritageExtensions.add(type.symbol) - } - if (type.aliasSymbol) { - heritageExtensions.add(type.aliasSymbol) - } - }) + else if (isUnitType(overlap) || 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 = overlap.flags & TypeFlags.Union ? countWhere((overlap as UnionType).types, isUnitType) : 1; + if (len >= matchingCount) { + bestMatch = target; + matchingCount = len; } } } - const type = getTypeOfNode(node); - if (type.symbol) { - heritageExtensions.add(type.symbol) - } - }) - }) + } + } + return bestMatch; } - function tryCacheUnionInheritance(members: Type[], parent: Type): void { - const parentSymbol = parent.symbol ?? parent.aliasSymbol; - if (parentSymbol) { - for (const member of members) { - const memberSymbol = member.symbol ?? member.aliasSymbol; - if (memberSymbol) { - if (!inheritanceSymbolCache.has(memberSymbol)) { - inheritanceSymbolCache.set(memberSymbol, new Set()); - } - inheritanceSymbolCache.get(memberSymbol)!.add(parentSymbol); - if (memberSymbol.declarations) { - for (const declaration of memberSymbol.declarations) { - if ((isInterfaceDeclaration(declaration) || isClassDeclaration(declaration)) && declaration.heritageClauses) { - tryCacheTsPlusInheritance(memberSymbol, declaration.heritageClauses); - } - } + + 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) { + if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) { + const match = getMatchingUnionConstituentForType(target as UnionType, source); + if (match) { + return match; + } + const sourceProperties = getPropertiesOfType(source); + if (sourceProperties) { + const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); + if (sourcePropertiesFiltered) { + const discriminated = discriminateTypeByDiscriminableItems(target as UnionType, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo); + if (discriminated !== target) { + return discriminated; } } } } + return undefined; } - function getTsPlusSourceFileCache(tag: string) { - return tsPlusFiles.has(tag) ? tsPlusFiles.get(tag)! : (tsPlusFiles.set(tag, new Set()), tsPlusFiles.get(tag)!); + + function getEffectivePropertyNameForPropertyNameNode(node: PropertyName) { + const name = getPropertyNameForPropertyNameNode(node); + return name ? name : + isComputedPropertyName(node) && isEntityNameExpression(node.expression) ? tryGetNameFromEntityNameExpression(node.expression) : undefined; } - function cacheTsPlusCompanion(declaration: ClassDeclaration | InterfaceDeclaration | TypeAliasDeclaration): void { - unresolvedCompanionDeclarations.add(declaration) + + + // + // TSPLUS START + // + function getPrimitiveTypeName(type: Type): string | undefined { + if (type.flags & TypeFlags.StringLike) { + return "String"; + } + if (type.flags & TypeFlags.NumberLike) { + return "Number"; + } + if (type.flags & TypeFlags.BooleanLike) { + return "Boolean"; + } + if (type.flags & TypeFlags.BigIntLike) { + return "BigInt"; + } } - function cacheTsPlusStaticVariable(file: SourceFile, declaration: VariableDeclarationWithIdentifier | ClassDeclarationWithIdentifier) { - const staticTags = collectTsPlusStaticTags(declaration); - if (staticTags.length > 0) { - const symbol = getSymbolAtLocation(declaration.name); - if (symbol) { - for (const { target, name } of staticTags) { - getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); - if (!unresolvedStaticCache.get(target)) { - unresolvedStaticCache.set(target, new Map()); - } - const map = unresolvedStaticCache.get(target)! - map.set(name, { - target, - name, - symbol, - declaration, - exportName: symbol.escapedName.toString(), - definition: file - }) + function getTextOfBinaryOp(kind: SyntaxKind): string | undefined { + return invertedBinaryOp[kind as keyof typeof invertedBinaryOp] as string | undefined; + } + function getCallExtension(node: Node) { + return callCache.get(node); + } + function isTailRec(node: Node) { + const links = getNodeLinks(node); + if (links.isTsPlusTailRec === undefined) { + for (const tag of getJSDocTags(node)) { + if (tag.tagName.escapedText === "tsplus" && typeof tag.comment === "string" && tag.comment === "tailRec") { + links.isTsPlusTailRec = true; + return links.isTsPlusTailRec; } } + links.isTsPlusTailRec = false; } + return links.isTsPlusTailRec; } - function cacheTsPlusFluentVariable(file: SourceFile, declaration: VariableDeclarationWithIdentifier) { - const fluentTags = collectTsPlusFluentTags(declaration); - for (const tag of fluentTags) { - getTsPlusSourceFileCache(tag.target).add(getSourceFileOfNode(declaration)); - if (!unresolvedFluentCache.has(tag.target)) { - unresolvedFluentCache.set(tag.target, new Map()); + function getFluentExtensionForPipeableSymbol(symbol: TsPlusPipeableIdentifierSymbol) { + const extension = fluentCache.get(symbol.tsPlusTypeName)?.get(symbol.tsPlusName)?.(); + if (extension && every(extension.signatures, (sig) => !sig.tsPlusPipeable)) { + return extension; + } + } + function intersectSets(sets: readonly Set[]): Set { + if (sets.length === 0) { + return new Set(); + } + if (sets.length === 1) { + return sets[0]; + } + + let shortest: Set | undefined; + for (const set of sets) { + if (shortest === undefined || shortest.size > set.size) { + shortest = set } - const map = unresolvedFluentCache.get(tag.target)!; - if (!map.has(tag.name)) { - map.set(tag.name, { - target: tag.target, - name: tag.name, - definition: new Set([{ - definition: file, - declaration: declaration as VariableDeclaration & { name: Identifier }, - exportName: declaration.name.escapedText.toString(), - priority: tag.priority, - }]) - }); + } + + // copy the shortest set, so as not to iterate over and delete items from the same set + const out = new Set(shortest) + + for (const set of sets) { + shortest!.forEach((a) => { + if (!set.has(a)) { + out.delete(a) + } + }) + } + + return out; + } + function collectRelevantSymbolsLoop(originalTarget: Type, lastNoInherit: Set, lastSeen?: Set) { + const seen: Set = new Set(lastSeen); + const noInherit: Set = new Set(lastNoInherit); + const relevant: Set = new Set(); + let queue: Type[] = [originalTarget] + while (queue.length > 0) { + const target = queue.shift()! + if (target.symbol) { + collectExcludedInheritance(target.symbol); } - else { - const extension = map.get(tag.name)!; - extension.definition.add({ - definition: file, - declaration: declaration as VariableDeclaration & { name: Identifier }, - exportName: declaration.name.escapedText.toString(), - priority: tag.priority, - }); + if (target.aliasSymbol) { + collectExcludedInheritance(target.aliasSymbol); } - } - } - function cacheTsPlusGetterVariable(file: SourceFile, declaration: VariableDeclarationWithIdentifier) { - for (const { target, name } of collectTsPlusGetterTags(declaration)) { - getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); - if (!getterCache.has(target)) { - getterCache.set(target, new Map()); + if (target.symbol && shouldInherit(target.symbol)) { + // Add the current symbol to the return Set + relevant.add(target.symbol) + // Check if the current type inherits other types + if (inheritanceSymbolCache.has(target.symbol)) { + inheritanceSymbolCache.get(target.symbol)!.forEach(addInheritedSymbol); + } + else if (target.symbol.declarations) { + target.symbol.declarations.forEach((declaration) => { + if ((isInterfaceDeclaration(declaration) || isClassDeclaration(declaration)) && declaration.heritageClauses) { + tryCacheTsPlusInheritance(target.symbol, declaration.heritageClauses); + if (inheritanceSymbolCache.has(target.symbol)) { + inheritanceSymbolCache.get(target.symbol)!.forEach(addInheritedSymbol); + } + } + }) + } + } + if (target.aliasSymbol && shouldInherit(target.aliasSymbol)) { + // Add the current symbol to the return Set + relevant.add(target.aliasSymbol); + if (inheritanceSymbolCache.has(target.aliasSymbol)) { + inheritanceSymbolCache.get(target.aliasSymbol)!.forEach(addInheritedSymbol) + } + // If the current type is a union type, add the inherited type symbols common to all members + if (target.flags & TypeFlags.Union) { + collectUnionType(target as UnionType); + } + // If the current type is an intersection type, simply enqueue all unseen members + if (target.flags & TypeFlags.Intersection) { + collectIntersectionType(target as IntersectionType) + } + } + if (!target.symbol && !target.aliasSymbol) { + if (target.flags & TypeFlags.Union) { + collectUnionType(target as UnionType); + } + if (target.flags & TypeFlags.Intersection) { + collectIntersectionType(target as IntersectionType) + } } - const map = getterCache.get(target)!; - map.set(name, { - patched: getTsPlusGetterSymbolForVariableDeclaration( - name, - (declaration as VariableDeclaration & { name: Identifier }) - ), - exportName: declaration.name.escapedText.toString(), - definition: file, - declaration, - }); } - } - function cacheTsPlusOperatorFunction(file: SourceFile, declaration: FunctionDeclaration) { - if(declaration.name && isIdentifier(declaration.name)) { - const operatorTags = collectTsPlusOperatorTags(declaration); - if (operatorTags.length > 0) { - const symbol = getSymbolAtLocation(declaration.name); - if (symbol) { - for (const tag of operatorTags) { - getTsPlusSourceFileCache(tag.target).add(getSourceFileOfNode(declaration)); - if (!operatorCache.has(tag.target)) { - operatorCache.set(tag.target, new Map()); + seen.clear(); + return relevant; + + function addInheritedSymbol(symbol: Symbol) { + if (shouldInherit(symbol) && symbol.declarations && symbol.declarations.length > 0) { + for (const decl of symbol.declarations) { + // Exclude all declarations but interface and class declarations. + // Symbol declarations can also include other declarations, such as variable declarations + // and module declarations, which we do not want to include for inheritance + if (isInterfaceDeclaration(decl) || isClassDeclaration(decl) || isTypeAliasDeclaration(decl)) { + const type = getTypeOfNode(decl); + if (seen.has(type)) { + continue; } - const map = operatorCache.get(tag.target)!; - if (!map.has(tag.name)) { - map.set(tag.name, []); + + if (!isErrorType(type)) { + seen.add(type); + queue.push(type); } - map.get(tag.name)!.push({ - patched: symbol, - exportName: declaration.name.escapedText.toString(), - definition: file, - priority: tag.priority, - }); } } } } - } - function cacheTsPlusOperatorVariable(file: SourceFile, declaration: VariableDeclarationWithIdentifier) { - const operatorTags = collectTsPlusOperatorTags(declaration); - if (operatorTags.length > 0) { - const symbol = getSymbolAtLocation(declaration.name); - if (symbol) { - for (const tag of operatorTags) { - getTsPlusSourceFileCache(tag.target).add(getSourceFileOfNode(declaration)); - if (!operatorCache.has(tag.target)) { - operatorCache.set(tag.target, new Map()); - } - const map = operatorCache.get(tag.target)!; - if (!map.has(tag.name)) { - map.set(tag.name, []); + + function collectUnionType(type: UnionType) { + const types = (type as UnionType).types; + const inherited: Set[] = [] + for (const member of types) { + if (!seen.has(member)) { + inherited.push(collectRelevantSymbolsLoop(member, noInherit, seen)); + } + } + // Add union members as "seen" only after the union has been collected + for (const member of types) { + seen.add(member); + } + + intersectSets(inherited).forEach((s) => { + shouldInherit(s) && relevant.add(s) + }) + } + + function collectIntersectionType(type: IntersectionType) { + for (const member of type.types) { + if (!seen.has(member)) { + seen.add(member); + queue.push(member); + } + } + } + + function collectExcludedInheritance(symbol: Symbol) { + if (symbol.declarations) { + symbol.declarations.forEach((declaration) => { + if ((isInterfaceDeclaration(declaration) || isTypeAliasDeclaration(declaration) || isClassDeclaration(declaration)) && + declaration.tsPlusNoInheritTags) { + declaration.tsPlusNoInheritTags.forEach((tag) => { + noInherit.add(tag) + }) } - map.get(tag.name)!.push({ - patched: symbol, - exportName: declaration.name.escapedText.toString(), - definition: file, - priority: tag.priority, - }); + }) + } + } + + function shouldInherit(symbol: Symbol) { + const tags = typeSymbolCache.get(symbol); + if (tags) { + for (const tag of tags) { + if (noInherit.has(tag)) return false; } } + return true; } } - function cacheTsPlusFluentFunction(file: SourceFile, declaration: FunctionDeclaration) { - const fluentTags = collectTsPlusFluentTags(declaration); - for (const tag of fluentTags) { - getTsPlusSourceFileCache(tag.target).add(getSourceFileOfNode(declaration)); - if (!unresolvedFluentCache.has(tag.target)) { - unresolvedFluentCache.set(tag.target, new Map()); + function collectRelevantSymbols(target: Type) { + const symbols = new Set(); + for (const symbol of collectRelevantSymbolsWorker(getBaseConstraintOrType(target))) { + symbols.add(symbol) + } + if (symbols.size === 0) { + for (const symbol of collectRelevantSymbolsWorker(target)) { + symbols.add(symbol) } - const map = unresolvedFluentCache.get(tag.target)!; - if (!map.has(tag.name)) { - map.set(tag.name, { - target: tag.target, - name: tag.name, - definition: new Set([{ - definition: file, - declaration, - exportName: declaration.name!.escapedText.toString(), - priority: tag.priority, - }]), - }); + } + return arrayFrom(symbols.values()); + } + /** + * Recursively collects the symbols associated with the given type. Index 0 is the given type's symbol, + * followed by subtypes, subtypes of subtypes, etc. + */ + function collectRelevantSymbolsWorker(target: Type) { + let returnArray = arrayFrom(collectRelevantSymbolsLoop(target, new Set()).values()) + + // collect primitive symbols last, in case they have overridden extensions + if (target.flags & TypeFlags.StringLike) { + returnArray.push(tsplusStringPrimitiveSymbol); + } + if (target.flags & TypeFlags.NumberLike) { + returnArray.push(tsplusNumberPrimitiveSymbol); + } + if (target.flags & TypeFlags.BooleanLike) { + returnArray.push(tsplusBooleanPrimitiveSymbol); + } + if (target.flags & TypeFlags.BigIntLike) { + returnArray.push(tsplusBigIntPrimitiveSymbol); + } + if (isFunctionType(target)) { + returnArray.push(tsplusFunctionPrimitiveSymbol); + } + if (isTupleType(target)) { + if (target.target.readonly) { + returnArray.push(tsplusReadonlyArraySymbol); } else { - const extension = map.get(tag.name)!; - extension.definition.add({ - definition: file, - declaration, - exportName: declaration.name!.escapedText.toString(), - priority: tag.priority, - }); + returnArray.push(tsplusArraySymbol); } } + if (target.flags & TypeFlags.Object) { + returnArray.push(tsplusObjectPrimitiveSymbol); + } + return returnArray } - function cacheTsPlusPipeableOperatorFunction(file: SourceFile, declaration: FunctionDeclaration) { - if (declaration.name && isIdentifier(declaration.name)) { - const pipeableOperatorTags = collectTsPlusPipeableOperatorTags(declaration); - if (pipeableOperatorTags.length > 0) { - for (const { target, name, priority } of pipeableOperatorTags) { - getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); - if (!unresolvedPipeableOperatorCache.has(target)) { - unresolvedPipeableOperatorCache.set(target, new Map()) + function getAllTypeTags(targetType: Type): string[] { + return targetType.symbol?.declarations?.flatMap(collectTsPlusTypeTags) ?? [] + } + function isInstanceType(type: Type): boolean { + if (!type.symbol) { + return true; + } + if (!(type.symbol.flags & SymbolFlags.Class)) { + return true; + } + const declaredType = getDeclaredTypeOfClassOrInterface(type.symbol); + if (!declaredType.symbol) { + return true; + } + return getTypeOfSymbol(declaredType.symbol) !== type; + } + function getExtensions(selfNode: Expression) { + const targetType: Type = getTypeOfNode(selfNode); + const isInstance = isInstanceType(targetType); + const symbols = collectRelevantSymbols(targetType); + const copy: Map = new Map(); + const copyFluent: Map> = new Map(); + const typeTags = getAllTypeTags(targetType) + symbols.forEach((target) => { + if (typeSymbolCache.has(target)) { + typeSymbolCache.get(target)!.forEach((typeSymbol) => { + if (!isInstance && typeTags.includes(typeSymbol)) { + return } - const exportName = declaration.name.escapedText.toString(); - let cached: [Type, TsPlusSignature[]] | false = false - const getTypeAndSignatures = (): [Type, TsPlusSignature[]] => { - if (cached === false) { - const resolved = getTsPlusFluentSignatureForPipeableFunction(file, exportName, name, declaration); - if (resolved) { - cached = resolved; + const _static = staticCache.get(typeSymbol); + if (_static) { + _static.forEach((v, k) => { + if (copy.has(k)) { + return; } - else { - error(declaration, Diagnostics.Invalid_declaration_annotated_as_pipeable); - cached = [errorType, []]; + const ext = v(); + if (ext) { + copy.set(k, ext.patched) } - } - return [cached[0], [...cached[1]]]; + }); } - const map = unresolvedPipeableOperatorCache.get(target)!; - if (!map.has(name)) { - map.set(name, { - target, - name, - definition: new Set() - }) + const _fluent = fluentCache.get(typeSymbol); + if (_fluent) { + _fluent.forEach((v, k) => { + const extension = v() + if (extension) { + if (isExtensionValidForTarget(getTypeOfSymbol(extension.patched), targetType)) { + if (!copyFluent.has(k)) { + copyFluent.set(k, new Set()); + } + copyFluent.get(k)!.add(extension); + } + } + }); } - map.get(name)!.definition.add({ - definition: file, - declaration, - exportName, - priority, - getTypeAndSignatures - }) - augmentPipeableIdentifierSymbol( - declaration.name, - target, - declaration.name.escapedText.toString(), - name, - () => getTypeAndSignatures()[0], - declaration - ); - } - } - } - } - function cacheTsPlusPipeableOperatorVariable(file: SourceFile, declaration: VariableDeclarationWithIdentifier) { - if (declaration.initializer && isFunctionLikeDeclaration(declaration.initializer) || - declaration.type && (isFunctionTypeNode(declaration.type) || isTypeLiteralNode(declaration.type))) { - const pipeableOperatorTags = collectTsPlusPipeableOperatorTags(declaration); - if (pipeableOperatorTags.length > 0) { - for (const { target, name, priority } of pipeableOperatorTags) { - getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); - if (!unresolvedPipeableOperatorCache.has(target)) { - unresolvedPipeableOperatorCache.set(target, new Map()) + const _getter = getterCache.get(typeSymbol); + if (_getter) { + _getter.forEach((v, k) => { + if (copy.has(k)) { + return; + } + const symbol = v.patched(selfNode); + if (symbol) { + copy.set(k, symbol); + } + }); } - const exportName = declaration.name.escapedText.toString(); - let cached: [Type, TsPlusSignature[]] | false = false - const getTypeAndSignatures = (): [Type, TsPlusSignature[]] => { - if (cached === false) { - const resolved = getTsPlusFluentSignatureForPipeableVariableDeclaration(file, exportName, name, declaration as VariableDeclarationWithFunction | VariableDeclarationWithFunctionType); - if (resolved) { - cached = resolved; + }); + } + if (companionSymbolCache.has(target) && isCompanionReference(selfNode)) { + companionSymbolCache.get(target)!.forEach((typeSymbol) => { + const _static = staticCache.get(typeSymbol); + if (_static) { + _static.forEach((v, k) => { + if (copy.has(k)) { + return; } - else { - error(declaration, Diagnostics.Invalid_declaration_annotated_as_pipeable); - cached = [errorType, []]; + const ext = v() + if (ext) { + copy.set(k, ext.patched) } - } - return [cached[0], [...cached[1]]]; + }); } - const map = unresolvedPipeableOperatorCache.get(target)!; - if (!map.has(name)) { - map.set(name, { - target, - name, - definition: new Set() - }) + }) + } + }) + fluentCache.get("global")?.forEach((v, k) => { + const extension = v() + if (extension) { + if (isExtensionValidForTarget(getTypeOfSymbol(extension.patched), targetType)) { + if (!copyFluent.has(k)) { + copyFluent.set(k, new Set()); } - map.get(name)!.definition.add({ - definition: file, - declaration, - exportName, - priority, - getTypeAndSignatures - }) - augmentPipeableIdentifierSymbol( - declaration.name, - target, - declaration.name.escapedText.toString(), - name, - () => getTypeAndSignatures()[0], - declaration as VariableDeclarationWithFunction - ); + copyFluent.get(k)!.add(extension); } } - } + }); + getterCache.get("global")?.forEach((v, k) => { + if (copy.has(k)) { + return; + } + const symbol = v.patched(selfNode); + if (symbol) { + copy.set(k, symbol); + } + }); + copyFluent.forEach((extensions, k) => { + copy.set( + k, + createTsPlusFluentSymbolWithType(k, arrayFrom(flatMapIterator(extensions.values(), (e) => getSignaturesOfType(getTypeOfSymbol(e.patched), SignatureKind.Call))) as TsPlusSignature[]) + ); + }); + copy.delete("__call"); + return copy; } - function cacheTsPlusPipeableFunction(file: SourceFile, declaration: FunctionDeclaration) { - if (declaration.name && isIdentifier(declaration.name)) { - const pipeableTags = collectTsPlusPipeableTags(declaration); - for (const { target, name, priority } of pipeableTags) { - getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); - if (!unresolvedPipeableCache.has(target)) { - unresolvedPipeableCache.set(target, new Map()); - } - const exportName = declaration.name.escapedText.toString() - let cached : [Type, TsPlusSignature[]] | false = false - const getTypeAndSignatures = (): [Type, TsPlusSignature[]] => { - if (cached === false) { - const resolved = getTsPlusFluentSignatureForPipeableFunction(file, exportName, name, declaration, /** thisify */ true); - if (resolved) { - cached = resolved; - } - else { - error(declaration, Diagnostics.Invalid_declaration_annotated_as_pipeable); - cached = [errorType, []]; - } - } - return [cached[0], [...cached[1]]]; - } - const map = unresolvedPipeableCache.get(target)!; - if (!map.has(name)) { - map.set(name, { target, name, definition: new Set() }) - } - const extension = map.get(name)! - extension.definition.add({ - definition: file, - declaration, - exportName, - priority, - getTypeAndSignatures - }) - augmentPipeableIdentifierSymbol( - declaration.name, - target, - declaration.name.escapedText.toString(), - name, - () => getTypeAndSignatures()[0], - declaration - ); - getNodeLinks(declaration).tsPlusPipeableExtension = { - declaration, - definition: file, - exportName, - typeName: target, - funcName: name, - getTypeAndSignatures + function isExtensionValidForTarget(extension: Type, targetType: Type): boolean { + return getSignaturesOfType(extension, SignatureKind.Call).find((candidate) => { + if (candidate.thisParameter) { + const paramType = unionIfLazy(getTypeOfSymbol(candidate.thisParameter)); + if (!candidate.typeParameters) { + return isTypeAssignableTo(targetType, paramType); + } else { + const inferenceContext = createInferenceContext( + candidate.typeParameters, + candidate, + InferenceFlags.None + ); + inferTypes(inferenceContext.inferences, targetType, paramType); + const instantiatedThisType = instantiateType(paramType, inferenceContext.mapper); + return isTypeAssignableTo(targetType, instantiatedThisType); } } - } + return false; + }) !== undefined; } - function cacheTsPlusPipeableVariable(file: SourceFile, declaration: VariableDeclarationWithIdentifier) { - if( - (declaration.initializer && isFunctionLikeDeclaration(declaration.initializer)) || - (declaration.type && isFunctionTypeNode(declaration.type)) || - ((declaration.type) && isTypeLiteralNode(declaration.type) && every(declaration.type.members, isCallSignatureDeclaration)) - ) { - for (const { target, name, priority } of collectTsPlusPipeableTags(declaration)) { - getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); - if (!unresolvedPipeableCache.has(target)) { - unresolvedPipeableCache.set(target, new Map()); - } - const map = unresolvedPipeableCache.get(target)!; - const exportName = declaration.name.escapedText.toString(); - let cached : [Type, TsPlusSignature[]] | false = false - const getTypeAndSignatures = () => { - if (cached === false) { - const resolved = getTsPlusFluentSignatureForPipeableVariableDeclaration( - file, - exportName, - name, - declaration as VariableDeclarationWithFunction | VariableDeclarationWithFunctionType, - /** thisify */ true - ) - if (resolved) { - cached = resolved; + function unionIfLazy(_paramType: Type) { + const isLazy = isLazyParameterByType(_paramType); + const paramType = isLazy ? getUnionType([_paramType, (_paramType as TypeReference).resolvedTypeArguments![0]], UnionReduction.None) : _paramType; + return paramType + } + function getFluentExtension(targetType: Type, name: string): Type | undefined { + const isInstance = isInstanceType(targetType); + const typeTags = getAllTypeTags(targetType) + const symbols = collectRelevantSymbols(targetType); + const candidates: Set = new Set(); + for (const target of symbols) { + if (typeSymbolCache.has(target)) { + const x = typeSymbolCache.get(target)!.flatMap( + (tag) => { + if (!isInstance && typeTags.includes(tag)) { + return [] } - else { - error(declaration, Diagnostics.Invalid_declaration_annotated_as_pipeable); - cached = [errorType, []]; + if (fluentCache.has(tag)) { + const cache = fluentCache.get(tag) + if (cache?.has(name)) { + return [cache.get(name)!] + } } + return [] } - return cached; - } - if (!map.has(name)) { - map.set(name, { target, name, definition: new Set() }) + ) + if (x.length === 0) { + continue; } - const extension = map.get(name)! - extension.definition.add({ - definition: file, - declaration, - exportName, - priority, - getTypeAndSignatures - }) - augmentPipeableIdentifierSymbol( - declaration.name, - target, - declaration.name.escapedText.toString(), - name, - () => getTypeAndSignatures()[0], - declaration as VariableDeclarationWithFunction - ); - getNodeLinks(declaration).tsPlusPipeableExtension = { - declaration: declaration as VariableDeclarationWithFunction, - definition: file, - exportName, - typeName: target, - funcName: name, - getTypeAndSignatures + else { + x.forEach((getExt) => { + const ext = getExt(); + if (ext) { + for (const signature of ext.signatures) { + candidates.add(signature); + } + } + }); } } } - } - function cacheTsPlusGetterFunction(file: SourceFile, declaration: FunctionDeclaration) { - if(declaration.name && isIdentifier(declaration.name)) { - for (const { target, name } of collectTsPlusGetterTags(declaration)) { - getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); - if (!getterCache.has(target)) { - getterCache.set(target, new Map()); - } - const map = getterCache.get(target)!; - map.set(name, { - patched: getTsPlusGetterSymbolForFunctionDeclaration(name, declaration), - exportName: declaration.name.escapedText.toString(), - definition: file, - declaration - }); + const globalExtension = fluentCache.get("global")?.get(name)?.(); + if (globalExtension) { + for (const signature of globalExtension.signatures) { + candidates.add(signature) } } + if (candidates.size > 0) { + return getTypeOfSymbol(createTsPlusFluentSymbolWithType(name, arrayFrom(candidates.values()))); + } } - function cacheTsPlusStaticFunction(file: SourceFile, declaration: FunctionDeclaration) { - if(declaration.name && isIdentifier(declaration.name)) { - for (const { target, name } of collectTsPlusStaticTags(declaration)) { - getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); - if (!staticCache.has(target)) { - staticCache.set(target, new Map()); - } - const map = staticCache.get(target)!; - map.set(name, () => { - if (staticFunctionCache.has(target)) { - const resolvedMap = staticFunctionCache.get(target)!; - if (resolvedMap.has(name)) { - return resolvedMap.get(name)!; + function getGetterExtension(targetType: Type, name: string) { + const symbols = collectRelevantSymbols(targetType) + const isInstance = isInstanceType(targetType); + const typeTags = getAllTypeTags(targetType) + for (const target of symbols) { + if (typeSymbolCache.has(target)) { + const x = typeSymbolCache.get(target)!.flatMap( + (tag) => { + if (!isInstance && typeTags.includes(tag)) { + return [] + } + if (getterCache.has(tag)) { + const cache = getterCache.get(tag) + if (cache?.has(name)) { + return [cache.get(name)!] + } } + return [] } - else { - staticFunctionCache.set(target, new Map()); - } - const resolvedMap = staticFunctionCache.get(target)!; - const [type, patched] = getTsPlusStaticSymbolForFunctionDeclaration( - file, - declaration.name!.escapedText.toString(), - name, - declaration - ); - const extension = { - patched, - exportName: declaration.name!.escapedText.toString(), - definition: file, - type - }; - resolvedMap.set(name, extension); - return extension; - }); + ) + if (x.length === 0) { + continue; + } + else { + return x[x.length - 1]; + } } } + return getterCache.get("global")?.get(name); } - function cacheTsPlusUnifyFunction(declaration: FunctionDeclaration) { - if(declaration.name && isIdentifier(declaration.name)) { - for (const target of collectTsPlusUnifyTags(declaration)) { - getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); - identityCache.set(target, declaration); + function getGetterCompanionExtension(targetType: Type, name: string) { + const symbols = collectRelevantSymbols(targetType) + for (const target of symbols) { + if (companionSymbolCache.has(target)) { + const x = companionSymbolCache.get(target)!.flatMap( + (tag) => { + if (getterCache.has(tag)) { + const cache = getterCache.get(tag) + if (cache?.has(name)) { + return [cache.get(name)!] + } + } + return [] + } + ) + if (x.length === 0) { + continue; + } + else { + return x[x.length - 1]; + } } } } - function cacheTsPlusIndexFunction(declaration: FunctionDeclaration) { - if(declaration.name && isIdentifier(declaration.name)) { - for (const target of collectTsPlusIndexTags(declaration)) { - getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); - indexCache.set(target, () => { - if (resolvedIndexCache.has(target)) { - return resolvedIndexCache.get(target); - } - const definition = getSourceFileOfNode(declaration); - const signatures = getSignaturesOfType(getTypeOfNode(declaration), SignatureKind.Call) as Signature[]; - if (signatures[0]) { - resolvedIndexCache.set(target, { declaration, signature: signatures[0], definition, exportName: declaration.name!.escapedText.toString() }); + function getStaticExtension(targetType: Type, name: string) { + const symbols = collectRelevantSymbols(targetType) + for (const target of symbols) { + if (typeSymbolCache.has(target)) { + const x = typeSymbolCache.get(target)!.flatMap( + (tag) => { + if (staticCache.has(tag)) { + const cache = staticCache.get(tag) + if (cache?.has(name)) { + return [cache.get(name)!] + } + } + return [] } - return resolvedIndexCache.get(target)!; - }); + ) + if (x.length === 0) { + continue; + } + else { + return x[x.length - 1](); + } } } } - function cacheTsPlusIndexVariable(declaration: VariableDeclarationWithIdentifier) { - for (const target of collectTsPlusIndexTags(declaration)) { - getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); - indexCache.set(target, () => { - if (resolvedIndexCache.has(target)) { - return resolvedIndexCache.get(target); + function getStaticCompanionExtension(targetType: Type, name: string) { + const symbols = collectRelevantSymbols(targetType) + for (const target of symbols) { + if (companionSymbolCache.has(target)) { + const x = companionSymbolCache.get(target)!.flatMap( + (tag) => { + if (staticCache.has(tag)) { + const cache = staticCache.get(tag) + if (cache?.has(name)) { + return [cache.get(name)!] + } + } + return [] + } + ) + if (x.length === 0) { + continue; } - const definition = getSourceFileOfNode(declaration); - const signatures = getSignaturesOfType(getTypeOfNode(declaration), SignatureKind.Call) as Signature[]; - if (signatures[0]) { - resolvedIndexCache.set(target, { declaration, signature: signatures[0], definition, exportName: declaration.name!.escapedText.toString() }); + else { + return x[x.length - 1](); } - return resolvedIndexCache.get(target)!; - }); + } } } - function cacheTsPlusPipeableIndexFunction(declaration: FunctionDeclaration) { - if(declaration.name && isIdentifier(declaration.name)) { - for (const target of collectTsPlusPipeableIndexTags(declaration)) { - getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); - indexCache.set(target, () => { - if (resolvedIndexCache.has(target)) { - return resolvedIndexCache.get(target); - } - const definition = getSourceFileOfNode(declaration); - const exportName = declaration.name!.escapedText.toString(); - const typeAndSignatures = getTsPlusFluentSignatureForPipeableFunction(definition, exportName, exportName, declaration); - if (typeAndSignatures && typeAndSignatures[1][0]) { - resolvedIndexCache.set(target, { declaration, signature: typeAndSignatures[1][0], definition, exportName }); - return resolvedIndexCache.get(target); + function isTransformablePipeableExtension(type: Type): boolean { + if (type.symbol) { + if (isTsPlusSymbol(type.symbol)) { + if (type.symbol.tsPlusTag === TsPlusSymbolTag.PipeableIdentifier) { + const fluent = getFluentExtensionForPipeableSymbol(type.symbol); + if (fluent) { + return true; } - }) + } + if (type.symbol.tsPlusTag === TsPlusSymbolTag.PipeableMacro) { + return true; + } } } + return false; } - function cacheTsPlusPipeableIndexVariable(declaration: VariableDeclarationWithIdentifier) { - for (const target of collectTsPlusPipeableIndexTags(declaration)) { - getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); - indexCache.set(target, () => { - if (resolvedIndexCache.has(target)) { - return resolvedIndexCache.get(target); - } - const definition = getSourceFileOfNode(declaration); - const exportName = declaration.name!.escapedText.toString(); - const typeAndSignatures = getTsPlusFluentSignatureForPipeableVariableDeclaration(definition, exportName, exportName, declaration as VariableDeclarationWithFunction | VariableDeclarationWithFunctionType); - if (typeAndSignatures && typeAndSignatures[1][0]) { - resolvedIndexCache.set(target, { declaration, signature: typeAndSignatures[1][0], definition, exportName }); - return resolvedIndexCache.get(target); + function markUsedParams(params: readonly TypeParameterDeclaration[], types: readonly Type[], cache: Set, node: Node) { + function visitor(node: Node): Node { + const t = getTypeOfNode(node); + for (let i = 0; i < params.length; i++) { + const type = types[i]; + if (isTypeIdenticalTo(type, t)) { + cache.add(params[i]); } - }) + } + return visitEachChild(node, visitor, nullTransformationContext); } + visitNode(node, visitor); } - function collectTsPlusSymbols(file: SourceFile): void { - for (const declaration of file.tsPlusContext.type) { - cacheTsPlusType(declaration); + function partitionTypeParametersForPipeable(dataFirst: FunctionDeclaration | ArrowFunction | FunctionExpression): [TypeParameterDeclaration[] | undefined, TypeParameterDeclaration[] | undefined] { + if (!dataFirst.typeParameters) { + return [undefined, undefined]; } - for (const declaration of file.tsPlusContext.companion) { - cacheTsPlusCompanion(declaration); + const typeParams = dataFirst.typeParameters; + const types = map(typeParams, getTypeOfNode); + const cache = new Set() + for (let i = 1; i < dataFirst.parameters.length; i++) { + const param = dataFirst.parameters[i]; + markUsedParams(typeParams, types, cache, param); } - for (const declaration of file.tsPlusContext.fluent) { - if (isFunctionDeclaration(declaration)) { - cacheTsPlusFluentFunction(file, declaration); - } - else { - cacheTsPlusFluentVariable(file, declaration); + let loop = true; + const processed = new Set(); + while (loop) { + const pre = cache.size; + cache.forEach((typeParam) => { + if (!processed.has(typeParam)) { + processed.add(typeParam); + if (typeParam.constraint) { + markUsedParams(typeParams, types, cache, typeParam.constraint); + } + if (typeParam.default) { + markUsedParams(typeParams, types, cache, typeParam.default); + } + } + }) + if (cache.size === pre) { + loop = false; } } - for (const declaration of file.tsPlusContext.pipeable) { - if (isFunctionDeclaration(declaration)) { - cacheTsPlusPipeableFunction(file, declaration); + const left: TypeParameterDeclaration[] = []; + const right: TypeParameterDeclaration[] = []; + forEach(dataFirst.typeParameters, (param) => { + if (cache.has(param)) { + left.push(param); } else { - cacheTsPlusPipeableVariable(file, declaration); - } - } - for (const declaration of file.tsPlusContext.operator) { - if (isFunctionDeclaration(declaration)) { - cacheTsPlusOperatorFunction(file, declaration); + right.push(param); } - else { - cacheTsPlusOperatorVariable(file, declaration); + }); + return [ + left.length > 0 ? left : undefined, + right.length > 0 ? right : undefined + ]; + } + function generatePipeable(declarationNode: VariableDeclaration, dataFirst: FunctionDeclaration | ArrowFunction | FunctionExpression): Type | undefined { + const signatures = getSignaturesOfType(getTypeOfNode(dataFirst), SignatureKind.Call) + if (signatures.length > 0) { + const returnExpression = createSyntheticExpression(dataFirst, getReturnTypeOfSignature(signatures[0])) + const [paramsFirst, paramsSecond] = partitionTypeParametersForPipeable(dataFirst); + const returnFunction = factory.createFunctionExpression( + undefined, + undefined, + undefined, + paramsSecond, + [dataFirst.parameters[0]], + undefined, + factory.createBlock( + [factory.createReturnStatement(returnExpression)], + true + ) + ); + setParent(returnFunction.body, returnFunction); + setParent((returnFunction.body).statements[0], returnFunction.body); + returnFunction.locals = createSymbolTable(); + const returnFunctionSymbol = createSymbol( + SymbolFlags.Function, + InternalSymbolName.Function + ); + returnFunction.symbol = returnFunctionSymbol; + returnFunctionSymbol.declarations = [returnFunction]; + returnFunctionSymbol.valueDeclaration = returnFunction; + const pipeable = factory.createFunctionDeclaration( + [factory.createModifier(SyntaxKind.DeclareKeyword)], + undefined, + dataFirst.name, + paramsFirst, + dataFirst.parameters.slice(1, dataFirst.parameters.length), + undefined, + factory.createBlock( + [factory.createReturnStatement(returnFunction)], + true + ) + ); + setParent(returnFunction, pipeable); + setParent(pipeable.body, pipeable); + setParent((pipeable.body as Block).statements[0], pipeable.body); + pipeable.locals = createSymbolTable(); + const pipeableSymbol = createSymbol( + SymbolFlags.Function, + InternalSymbolName.Function + ) as TsPlusPipeableMacroSymbol; + pipeableSymbol.tsPlusTag = TsPlusSymbolTag.PipeableMacro; + pipeableSymbol.tsPlusDeclaration = declarationNode; + pipeableSymbol.tsPlusDataFirst = dataFirst; + pipeableSymbol.tsPlusSourceFile = getSourceFileOfNode(dataFirst); + pipeableSymbol.tsPlusExportName = dataFirst.name!.escapedText.toString(); + pipeable.symbol = pipeableSymbol; + pipeableSymbol.declarations = [pipeable]; + setParent(pipeable, declarationNode); + const declarationSymbol = getSymbolAtLocation(declarationNode) + if (declarationSymbol) { + declarationSymbol.declarations = [pipeable] + declarationSymbol.valueDeclaration = pipeable } + return getTypeOfNode(pipeable); } - for (const declaration of file.tsPlusContext.pipeableOperator) { - if (isFunctionDeclaration(declaration)) { - cacheTsPlusPipeableOperatorFunction(file, declaration); - } - else { - cacheTsPlusPipeableOperatorVariable(file, declaration); - } + } + function isTsPlusMacroGetter(node: Node, macro: string): boolean { + const links = getNodeLinks(node) + return !!links.tsPlusGetterExtension && + !!links.tsPlusGetterExtension.declaration.tsPlusMacroTags && + links.tsPlusGetterExtension.declaration.tsPlusMacroTags.includes(macro); + } + function isTsPlusMacroCall(node: Node, macro: K): node is TsPlusMacroCallExpression { + if (!isCallExpression(node) && !isBinaryExpression(node)) { + return false; } - for (const declaration of file.tsPlusContext.static) { - if (isFunctionDeclaration(declaration)) { - cacheTsPlusStaticFunction(file, declaration); - } - else { - cacheTsPlusStaticVariable(file, declaration); - } + const links = getNodeLinks(isCallExpression(node) ? node : node.operatorToken); + if ( + links.resolvedSignature && + links.resolvedSignature.declaration && + collectTsPlusMacroTags(links.resolvedSignature.declaration).findIndex((tag) => tag === macro) !== -1 + ) { + return true; } - for (const declaration of file.tsPlusContext.getter) { - if (isFunctionDeclaration(declaration)) { - cacheTsPlusGetterFunction(file, declaration); - } - else { - cacheTsPlusGetterVariable(file, declaration); - } + return false; + } + function isCompanionReference(node: Expression | QualifiedName): boolean { + let type: Type | undefined + + const symbol = getSymbolAtLocation(node); + if (symbol) { + type = getTypeOfSymbol(symbol); } - for (const declaration of file.tsPlusContext.unify) { - cacheTsPlusUnifyFunction(declaration); + else { + type = getNodeLinks(node).tsPlusResolvedType; } - for (const declaration of file.tsPlusContext.index) { - if (isFunctionDeclaration(declaration)) { - cacheTsPlusIndexFunction(declaration); - } - else { - cacheTsPlusIndexVariable(declaration); - } + + if (!type) { + return false } - for (const declaration of file.tsPlusContext.pipeableIndex) { - if (isFunctionDeclaration(declaration)) { - cacheTsPlusPipeableIndexFunction(declaration); - } - else { - cacheTsPlusPipeableIndexVariable(declaration); + + // Class companion object + if (getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class) { + return true + } + + // Synthetic Interface or TypeAlias companion object + if (symbol && symbol.declarations && symbol.declarations.length > 0) { + const declaration = symbol.declarations[0] + if (isInterfaceDeclaration(declaration) || isTypeAliasDeclaration(declaration)) { + return !!getSymbolLinks(symbol).isPossibleCompanionReference; } } + + return false } - function fillTsPlusLocalScope(file: SourceFile) { - const derivationRules = tsPlusWorldScope.rules; - if (file.symbol) { - const exports = file.symbol.exports; - if (exports) { - exports.forEach((exportSymbol) => { - if (exportSymbol.valueDeclaration) { - const declaration = exportSymbol.valueDeclaration; - if (isTsPlusImplicit(declaration)) { - indexInScope(exportSymbol); - } - else if((isFunctionDeclaration(declaration) || isVariableDeclaration(declaration)) && declaration.tsPlusDeriveTags) { - for (const tag of declaration.tsPlusDeriveTags) { - const match = tag.match(/^derive ([^<]*)<([^>]*)> (.*)$/); - if (match) { - const [, typeTag, paramActions, rest] = match; - const [priority, ...options] = rest.split(" "); - if (!derivationRules.has(typeTag)) { - derivationRules.set(typeTag, { lazyRule: void 0, rules: [] }); - } - derivationRules.get(typeTag)?.rules.push([ - { typeTag, paramActions: paramActions.split(",").map((s) => s.trim()) }, - Number.parseFloat(priority), - declaration, - new Set(options) - ]); - } else { - const match = tag.match(/^derive ([^<]*) lazy$/); - if (match) { - const [, typeTag] = match; - if (!derivationRules.has(typeTag)) { - derivationRules.set(typeTag, { lazyRule: void 0, rules: [] }); - } - derivationRules.get(typeTag)!.lazyRule = declaration; - } - } - } - } - } + function checkTsPlusCustomCall( + declaration: Declaration, + errorNode: Node, + args: Expression[], + checkMode: CheckMode | undefined, + signature?: Signature, + addDiagnostic?: (_: Diagnostic) => void, + ): Type { + const funcType = getTypeOfNode(declaration); + const apparentType = getApparentType(funcType); + const candidate = signature ?? getSignaturesOfType(apparentType, SignatureKind.Call)[0]!; + if (!candidate) { + return errorType; + } + const node = factory.createCallExpression( + factory.createIdentifier("$tsplus_custom_call"), + [], + args + ); + setTextRange(node, errorNode) + setParent(node, declaration.parent); + if (candidate.typeParameters) { + const inferenceContext = createInferenceContext( + candidate.typeParameters, + candidate, + InferenceFlags.None + ); + const typeArgumentTypes = inferTypeArguments( + node, + candidate, + args, + checkMode || CheckMode.Normal, + inferenceContext + ); + const signature = getSignatureInstantiation( + candidate, + typeArgumentTypes, + /*isJavascript*/ false, + inferenceContext && inferenceContext.inferredTypeParameters + ); + const digs = getSignatureApplicabilityError( + node, + args, + signature, + assignableRelation, + checkMode || CheckMode.Normal, + /*reportErrors*/ addDiagnostic ? true : false, + /*containingMessageChain*/ void 0 + ); + if (digs) { + digs.forEach((dig) => { + addDiagnostic?.(dig); }); + return errorType; } + return getReturnTypeOfSignature(signature); + } + const digs = getSignatureApplicabilityError( + node, + args, + candidate, + assignableRelation, + CheckMode.Normal, + /*reportErrors*/ true, + /*containingMessageChain*/ void 0 + ); + if (digs) { + digs.forEach((dig) => { + addDiagnostic?.(dig); + }); + return errorType; + } + return getReturnTypeOfSignature(candidate); + } + function getInstantiatedTsPlusSignature( + declaration: Declaration, + args: Expression[], + checkMode: CheckMode | undefined, + ): Signature { + const funcType = getTypeOfNode(declaration); + const apparentType = getApparentType(funcType); + const candidate = getSignaturesOfType(apparentType, SignatureKind.Call)[0]!; + const node = factory.createCallExpression( + factory.createIdentifier("$tsplus_custom_call"), + [], + args + ); + setParent(node, declaration.parent); + if (candidate.typeParameters) { + const inferenceContext = createInferenceContext( + candidate.typeParameters, + candidate, + InferenceFlags.None + ); + const typeArgumentTypes = inferTypeArguments( + node, + candidate, + args, + checkMode || CheckMode.Normal, + inferenceContext + ); + const signature = getSignatureInstantiation( + candidate, + typeArgumentTypes, + /*isJavascript*/ false, + inferenceContext && inferenceContext.inferredTypeParameters + ); + return signature; } + return candidate; } - function initTsPlusTypeCheckerImplicits() { - for (const file of host.getSourceFiles()) { - fillTsPlusLocalScope(file); + + function computeUnifiedType(unifier: FunctionDeclaration, union: Type) { + for (const signature of getSignaturesOfType(getTypeOfNode(unifier), SignatureKind.Call)) { + if (signature.minArgumentCount === 1 && signature.typeParameters) { + const context = createInferenceContext(signature.typeParameters, signature, InferenceFlags.None); + inferTypes(context.inferences, union, getTypeOfSymbol(signature.parameters[0])); + const instantiated = getSignatureInstantiation(signature, getInferredTypes(context), /*isJavascript*/ false) + if (isTypeAssignableTo(union, getTypeOfSymbol(instantiated.parameters[0]))) { + return getReturnTypeOfSignature(instantiated); + } + } } + return errorType; } - function postInitTsPlusTypeChecker() { - tsPlusDebug && console.time("initTsPlusTypeChecker build dependency tree") - typeSymbolCache.forEach((tags, symbol) => { - forEach(symbol.declarations, (declaration) => { - const source = getSourceFileOfNode(declaration); - const set = tsPlusFilesFinal.has(source) ? tsPlusFilesFinal.get(source)! : (tsPlusFilesFinal.set(source, new Set()), tsPlusFilesFinal.get(source)!); - forEach(tags, (tag) => { - tsPlusFiles.get(tag)?.forEach((dep) => { - set.add(dep); - }) - }) - }) - }) - tsPlusDebug && console.timeEnd("initTsPlusTypeChecker build dependency tree") - } - function initTsPlusTypeChecker() { - if (compilerOptions.tsPlusEnabled === false) { - return; + + function getUnifiedType(unionType: UnionType): Type { + if (unionType.tsPlusUnified) { + return unionType; } - tsPlusDebug && console.time("initTsPlusTypeChecker caches") - fileMap.map = getFileMap(host.getCompilerOptions(), host); - fluentCache.clear(); - unresolvedFluentCache.clear(); - unresolvedPipeableCache.clear(); - unresolvedPipeableOperatorCache.clear(); - operatorCache.clear(); - typeSymbolCache.clear(); - typeHashCache.clear(); - staticFunctionCache.clear(); - staticValueCache.clear(); - unresolvedStaticCache.clear(); - identityCache.clear(); - tsPlusFiles.clear(); - tsPlusFilesFinal.clear(); - getterCache.clear(); - callCache.clear(); - indexCache.clear(); - resolvedIndexCache.clear(); - indexAccessExpressionCache.clear(); - inheritanceSymbolCache.clear(); - tsPlusWorldScope.implicits.clear(); - tsPlusWorldScope.rules.clear(); - tsPlusDebug && console.timeEnd("initTsPlusTypeChecker caches") - tsPlusDebug && console.time("initTsPlusTypeChecker collect") - for (const file of host.getSourceFiles()) { - collectTsPlusSymbols(file); + let type = unionType.types[0]; + while (type.flags & TypeFlags.Union) { + type = (type as UnionType).types[0]; } - tsPlusDebug && console.timeEnd("initTsPlusTypeChecker collect") - tsPlusDebug && console.time("initTsPlusTypeChecker joinining signatures") - - const unresolvedUnionInheritance = new Map() - const unresolvedInheritance = new Map>() - - unresolvedTypeDeclarations.forEach((declaration) => { - const type = getTypeOfNode(declaration); - for (const typeTag of collectTsPlusTypeTags(declaration)) { - if (type === globalStringType) { - addToTypeSymbolCache(tsplusStringPrimitiveSymbol, typeTag, "after"); - } - if (type === globalNumberType) { - addToTypeSymbolCache(tsplusNumberPrimitiveSymbol, typeTag, "after"); - } - if (type === globalBooleanType) { - addToTypeSymbolCache(tsplusBooleanPrimitiveSymbol, typeTag, "after"); - } - if (type === getGlobalBigIntType()) { - addToTypeSymbolCache(tsplusBigIntPrimitiveSymbol, typeTag, "after"); - } - if (type === globalFunctionType) { - addToTypeSymbolCache(tsplusFunctionPrimitiveSymbol, typeTag, "after"); - } - if (type === globalObjectType) { - addToTypeSymbolCache(tsplusObjectPrimitiveSymbol, typeTag, "after"); - } - if (type === globalArrayType) { - addToTypeSymbolCache(tsplusArraySymbol, typeTag, "after"); - } - if (type === globalReadonlyArrayType) { - addToTypeSymbolCache(tsplusReadonlyArraySymbol, typeTag, "after"); - } - if (type.symbol) { - addToTypeSymbolCache(type.symbol, typeTag, "after"); - if ((isInterfaceDeclaration(declaration) || isClassDeclaration(declaration)) && declaration.heritageClauses) { - unresolvedInheritance.set(type.symbol, declaration.heritageClauses); - } - } - if (type.aliasSymbol) { - addToTypeSymbolCache(type.aliasSymbol, typeTag, "after"); - } - if (type.flags & TypeFlags.Union) { - unresolvedUnionInheritance.set(type, (type as UnionType).types) - } - if (type.flags & TypeFlags.UnionOrIntersection) { - const types = (type as UnionOrIntersectionType).types; - for (const member of types) { - if (member.symbol) { - addToTypeSymbolCache(member.symbol, typeTag, "before"); - } - if (member.aliasSymbol) { - addToTypeSymbolCache(member.aliasSymbol, typeTag, "before"); + const targetSymbol = type.symbol || type.aliasSymbol; + if (targetSymbol) { + for (const declaration of (targetSymbol.declarations ?? [])) { + for (let target of collectTsPlusTypeTags(declaration)) { + const id = identityCache.get(target); + if (id) { + const unified = computeUnifiedType(id, unionType); + if ((unified.flags & TypeFlags.Union) && unionType.types.length < (unified as UnionType).types.length) { + return unionType; } + return unified; } } } - }) - unresolvedTypeDeclarations.clear(); + } + unionType.tsPlusUnified = true; + return unionType; + } + function tsPlusShouldMarkIdentifierAliasReferenced(node: Identifier, type: Type): boolean { + if (node.parent && isCallExpression(node.parent) && node === node.parent.expression && type.symbol && type.symbol.valueDeclaration) { + if (collectTsPlusMacroTags(type.symbol.valueDeclaration).find((tag) => tag === "pipe")) { + return false; + } + } + if ( + !(node.parent && + isCallExpression(node.parent) && + node.parent.expression === node && + getSignaturesOfType(type, SignatureKind.Call).length === 0 && + ( + (getStaticExtension(type, "__call") != null) || + (getStaticCompanionExtension(type, "__call") != null) + ) + ) && + !isTransformablePipeableExtension(type) + ) { + return true; + } + return false; + } + function checkIdentifier(node: Identifier, checkMode: CheckMode | undefined): Type { + if (isThisInTypeQuery(node)) { + return checkThisExpression(node); + } - unresolvedCompanionDeclarations.forEach((declaration) => { - const tags = collectTsPlusCompanionTags(declaration); - const type = getTypeOfNode(declaration) - if (type.symbol) { - for (const companionTag of tags) { - getTsPlusSourceFileCache(companionTag).add(getSourceFileOfNode(declaration)); - addToCompanionSymbolCache(type.symbol, companionTag); - } + const type = checkIdentifierOriginal(node, checkMode); + // 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)) { + // We should also not mark as used identifiers that will be replaced + if (tsPlusShouldMarkIdentifierAliasReferenced(node, type)) { + markAliasReferenced(getResolvedSymbol(node), node); } - if (type.aliasSymbol) { - for (const companionTag of tags) { - getTsPlusSourceFileCache(companionTag).add(getSourceFileOfNode(declaration)); - addToCompanionSymbolCache(type.aliasSymbol, companionTag); - } + } + return type; + } + function checkPropertyAccessForExtension(node: PropertyAccessExpression | QualifiedName, _left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier, _checkMode: CheckMode | undefined) { + const inType = getPropertiesOfType(leftType).findIndex((p) => p.escapedName === right.escapedText) !== -1; + if (!inType) { + const nodeLinks = getNodeLinks(node); + if (nodeLinks.tsPlusResolvedType) { + return nodeLinks.tsPlusResolvedType; } - }) - unresolvedCompanionDeclarations.clear() - - unresolvedStaticCache.forEach((map, typeName) => { - if (!staticCache.has(typeName)) { - staticCache.set(typeName, new Map()); + if (isCompanionReference(_left)) { + const companionExt = getStaticCompanionExtension(leftType, right.escapedText.toString()); + if (companionExt) { + nodeLinks.tsPlusStaticExtension = companionExt; + nodeLinks.tsPlusResolvedType = companionExt.type + return companionExt.type; + } } - const staticMap = staticCache.get(typeName)!; - map.forEach(({ target, declaration, name, definition, exportName }) => { - staticMap.set(name, () => { - if (staticValueCache.has(target)) { - const resolvedMap = staticValueCache.get(target)!; - if (resolvedMap.has(name)) { - return resolvedMap.get(name)!; - } - } - else { - staticValueCache.set(target, new Map()); - } - const resolvedMap = staticValueCache.get(typeName)!; - const nameSymbol = getSymbolAtLocation(declaration.name!); - if (nameSymbol) { - const patched = createTsPlusStaticValueSymbol(name, declaration, nameSymbol); - if (isClassDeclaration(declaration)) { - const companionTags = collectTsPlusCompanionTags(declaration); - for (const companionTag of companionTags) { - getTsPlusSourceFileCache(companionTag).add(getSourceFileOfNode(declaration)); - addToCompanionSymbolCache(patched, companionTag); - } - } - const type = getTypeOfSymbol(patched); - // @ts-expect-error - type.tsPlusSymbol = patched; - const extension = { - patched, - definition, - exportName, - type - } - resolvedMap.set(name, extension); - return extension; + const fluentExtType = getFluentExtension(leftType, right.escapedText.toString()); + if (fluentExtType && isCallExpression(node.parent) && node.parent.expression === node) { + if (isExtensionValidForTarget(fluentExtType, leftType)) { + if (isIdentifier(_left)) { + markAliasReferenced(getResolvedSymbol(_left), _left); } - }) - }) - }) - unresolvedFluentCache.forEach((map, typeName) => { - if (!fluentCache.has(typeName)) { - fluentCache.set(typeName, new Map()); + nodeLinks.tsPlusResolvedType = fluentExtType; + nodeLinks.isFluent = true; + return fluentExtType; + } + return; } - const fluentMap = fluentCache.get(typeName)!; - map.forEach(({ name, definition }) => { - fluentMap.set(name, () => { - if (resolvedFluentCache.has(typeName)) { - const resolvedMap = resolvedFluentCache.get(typeName)!; - if (resolvedMap.has(name)) { - return resolvedMap.get(name)!; - } + const getterExt = getGetterExtension(leftType, right.escapedText.toString()) + if (getterExt && isExpression(_left)) { + const symbol = getterExt.patched(_left); + if (symbol) { + if (isIdentifier(_left)) { + markAliasReferenced(getResolvedSymbol(_left), _left); } - const allTypes: { type: Type, signatures: readonly TsPlusSignature[] }[] = []; - const prioritizedSignaturesMap = new Map () - - definition.forEach(({ declaration, definition, exportName, priority }) => { - if (isFunctionDeclaration(declaration)) { - const typeAndSignatures = getTsPlusFluentSignaturesForFunctionDeclaration(definition, exportName, declaration); - if (typeAndSignatures) { - const [type, signatures] = typeAndSignatures; - if (prioritizedSignaturesMap.has(priority)) { - prioritizedSignaturesMap.get(priority)!.push(...signatures) - } - else { - prioritizedSignaturesMap.set(priority, signatures); - } - allTypes.push({ type, signatures }); - } - } - else { - const typeAndSignatures = getTsPlusFluentSignaturesForVariableDeclaration(definition, exportName, declaration); - if (typeAndSignatures) { - const [type, signatures] = typeAndSignatures; - if (prioritizedSignaturesMap.has(priority)) { - prioritizedSignaturesMap.get(priority)!.push(...signatures) - } - else { - prioritizedSignaturesMap.set(priority, signatures); - } - allTypes.push({ type, signatures }); - } + const type = getTypeOfSymbol(symbol); + nodeLinks.tsPlusGetterExtension = getterExt; + nodeLinks.tsPlusResolvedType = type; + nodeLinks.tsPlusSymbol = symbol; + return type; + } + } + const staticExt = getStaticExtension(leftType, right.escapedText.toString()); + if (staticExt) { + nodeLinks.tsPlusStaticExtension = staticExt; + nodeLinks.tsPlusResolvedType = staticExt.type; + return staticExt.type; + } + } + } + function checkFluentPipeableAgreement(pipeableExtension: TsPlusPipeableExtension) { + if (!fluentCache.has(pipeableExtension.typeName)) { + return; + } + const fluentMap = fluentCache.get(pipeableExtension.typeName)!; + if (!fluentMap.has(pipeableExtension.funcName)) { + return; + } + const fluentExtension = fluentMap.get(pipeableExtension.funcName)!(); + if (!fluentExtension || some(fluentExtension.types, ({ type: fluentType }) => isTypeAssignableTo(fluentType, pipeableExtension.getTypeAndSignatures()[0]))) { + return; + } + else { + error(pipeableExtension.declaration, Diagnostics.Declaration_annotated_as_pipeable_is_not_assignable_to_its_corresponding_fluent_declaration); + return; + } + } + function isLazyParameterByType(type: Type): type is TypeReference { + if (type.symbol && type.symbol.declarations && type.symbol.declarations.length > 0) { + const tag = collectTsPlusTypeTags(type.symbol.declarations[0])[0]; + if (tag === "tsplus/LazyArgument") { + return true; + } + } + return false; + } + function tryCacheOptimizedPipeableCall(node: CallExpression): void { + if (isIdentifier(node.expression)) { + const identifierType = getTypeOfNode(node.expression); + const identifierSymbol = identifierType.symbol; + if (identifierSymbol && isTsPlusSymbol(identifierSymbol)) { + if (identifierSymbol.tsPlusTag === TsPlusSymbolTag.PipeableIdentifier) { + const fluentExtension = checker.getFluentExtensionForPipeableSymbol(identifierSymbol); + if (fluentExtension) { + const signature = find(fluentExtension.types, ({ type }) => checker.isTypeAssignableTo(identifierSymbol.getTsPlusDataFirstType(), type))?.signatures[0]; + if (signature) { + getNodeLinks(node).tsPlusOptimizedDataFirst = { + definition: signature.tsPlusFile, + exportName: signature.tsPlusExportName + }; } - }); - - const prioritizedSignatures: [number, TsPlusSignature[]][] = [] - prioritizedSignaturesMap.forEach((signatures, priority) => { - prioritizedSignatures.push([priority, signatures]) - }) - prioritizedSignatures.sort((x, y) => x[0] > y[0] ? 1 : x[0] < y[0] ? -1 : 0) - - const allSignatures = prioritizedSignatures.flatMap((signatures) => signatures[1]) - - if (allSignatures.length === 0) { - return undefined; } - const symbol = createTsPlusFluentSymbol(name, allSignatures); - const type = createAnonymousType(symbol, emptySymbols, allSignatures, [], []); - const extension: TsPlusFluentExtension = { - patched: createSymbolWithType(symbol, type), - signatures: allSignatures, - types: allTypes + } + if (identifierSymbol.tsPlusTag === TsPlusSymbolTag.PipeableMacro) { + getNodeLinks(node).tsPlusOptimizedDataFirst = { + definition: identifierSymbol.tsPlusSourceFile, + exportName: identifierSymbol.tsPlusExportName }; - if (!resolvedFluentCache.has(typeName)) { - resolvedFluentCache.set(typeName, new Map()); - } - const resolvedMap = resolvedFluentCache.get(typeName)!; - resolvedMap.set(name, extension); - return extension; - }); - }); - }); - unresolvedFluentCache.clear(); - unresolvedPipeableCache.forEach((map, typeName) => { - if (!fluentCache.has(typeName)) { - fluentCache.set(typeName, new Map()); - } - const fluentMap = fluentCache.get(typeName)!; - map.forEach(({ name, definition }) => { - if (!fluentMap.has(name)) { - fluentMap.set(name, () => { - if (resolvedFluentCache.has(typeName)) { - const resolvedMap = resolvedFluentCache.get(typeName)!; - if (resolvedMap.has(name)) { - return resolvedMap.get(name)!; - } - } - const allTypes: { type: Type, signatures: readonly TsPlusSignature[] }[] = []; - const prioritizedSignaturesMap = new Map () - - definition.forEach(({ priority, getTypeAndSignatures }) => { - const typeAndSignatures = getTypeAndSignatures(); - if (typeAndSignatures) { - const [type, signatures] = typeAndSignatures; - if (prioritizedSignaturesMap.has(priority)) { - prioritizedSignaturesMap.get(priority)!.push(...signatures) - } - else { - prioritizedSignaturesMap.set(priority, signatures); - } - allTypes.push({ type, signatures }); - } - }); - - const prioritizedSignatures: [number, TsPlusSignature[]][] = [] - prioritizedSignaturesMap.forEach((signatures, priority) => { - prioritizedSignatures.push([priority, signatures]) - }) - prioritizedSignatures.sort((x, y) => x[0] > y[0] ? 1 : x[0] < y[0] ? -1 : 0) - - const allSignatures = prioritizedSignatures.flatMap((signatures) => signatures[1]) - - if (allSignatures.length === 0) { - return undefined; - } - const symbol = createTsPlusFluentSymbol(name, allSignatures); - const type = createAnonymousType(symbol, emptySymbols, allSignatures, [], []); - const extension: TsPlusFluentExtension = { - patched: createSymbolWithType(symbol, type), - signatures: allSignatures, - types: allTypes - }; - if (!resolvedFluentCache.has(typeName)) { - resolvedFluentCache.set(typeName, new Map()); - } - const resolvedMap = resolvedFluentCache.get(typeName)!; - resolvedMap.set(name, extension); - return extension; - }); } - }); - }); - unresolvedPipeableCache.clear(); - unresolvedPipeableOperatorCache.forEach((map, typeName) => { - if (!operatorCache.has(typeName)) { - operatorCache.set(typeName, new Map()); } - const cache = operatorCache.get(typeName)!; - map.forEach((member, funcName) => { - if (!cache.has(funcName)) { - cache.set(funcName, []); - } - member.definition.forEach(({ priority, getTypeAndSignatures, exportName, definition }) => { - const [memberType, memberSignatures] = getTypeAndSignatures(); - const symbol = createSymbol(SymbolFlags.Function, funcName as __String); - symbol.declarations = memberSignatures.map((sig) => sig.declaration!); - getSymbolLinks(symbol).type = memberType; - cache.get(funcName)!.push({ - patched: symbol, - exportName, - definition, - priority - }) - }) - }) - }) - unresolvedPipeableOperatorCache.clear(); - operatorCache.forEach((map) => { - map.forEach((extensions) => { - extensions.sort(({ priority: x }, { priority: y }) => x > y ? 1 : x < y ? -1 : 0) - }) - }) - - unresolvedUnionInheritance.forEach((types, type) => { - tryCacheUnionInheritance(types, type); - }); - unresolvedUnionInheritance.clear(); - - unresolvedInheritance.forEach((heritageClauses, symbol) => { - tryCacheTsPlusInheritance(symbol, heritageClauses); - }); - unresolvedInheritance.clear() - - tsPlusDebug && console.timeEnd("initTsPlusTypeChecker joinining signatures") - tsPlusDebug && console.time("initTsPlusTypeChecker implicits") - initTsPlusTypeCheckerImplicits(); - tsPlusDebug && console.timeEnd("initTsPlusTypeChecker implicits") - postInitTsPlusTypeChecker(); - } - // TSPLUS EXTENSION END - - function initializeTypeChecker() { - tsPlusGlobalImportCache.clear(); - // Bind all source files and propagate errors - for (const file of host.getSourceFiles()) { - bindSourceFile(file, compilerOptions); - for (const statement of file.imports) { - if (isImportDeclaration(statement.parent)) { - tryCacheTsPlusGlobalSymbol(statement.parent); + } + if (isPropertyAccessExpression(node.expression) && isIdentifier(node.expression.name)) { + const identifierType = checker.getTypeAtLocation(node.expression.name); + const identifierSymbol = identifierType.symbol; + if (identifierSymbol && isTsPlusSymbol(identifierSymbol)) { + if (identifierSymbol.tsPlusTag === TsPlusSymbolTag.PipeableIdentifier) { + const fluentExtension = checker.getFluentExtensionForPipeableSymbol(identifierSymbol); + if (fluentExtension) { + const signature = find(fluentExtension.types, ({ type }) => checker.isTypeAssignableTo(identifierSymbol.getTsPlusDataFirstType(), type))?.signatures[0]; + if (signature) { + getNodeLinks(node).tsPlusOptimizedDataFirst = { + definition: signature.tsPlusFile, + exportName: signature.tsPlusExportName + }; + } + } + } + if (identifierSymbol.tsPlusTag === TsPlusSymbolTag.PipeableMacro) { + getNodeLinks(node).tsPlusOptimizedDataFirst = { + definition: identifierSymbol.tsPlusSourceFile, + exportName: identifierSymbol.tsPlusExportName + }; + } + if (identifierSymbol.tsPlusTag === TsPlusSymbolTag.StaticFunction) { + const declType = checker.getTypeAtLocation(identifierSymbol.tsPlusDeclaration.name!); + const declSym = declType.symbol; + if (declSym && isTsPlusSymbol(declSym)) { + if (declSym.tsPlusTag === TsPlusSymbolTag.PipeableIdentifier) { + const fluentExtension = checker.getFluentExtensionForPipeableSymbol(declSym); + if (fluentExtension) { + const signature = find(fluentExtension.types, ({ type }) => checker.isTypeAssignableTo(declSym.getTsPlusDataFirstType(), type))?.signatures[0]; + if (signature) { + getNodeLinks(node).tsPlusOptimizedDataFirst = { + definition: signature.tsPlusFile, + exportName: signature.tsPlusExportName + }; + } + } + } + if (declSym.tsPlusTag === TsPlusSymbolTag.PipeableMacro) { + getNodeLinks(node).tsPlusOptimizedDataFirst = { + definition: declSym.tsPlusSourceFile, + exportName: declSym.tsPlusExportName + }; + } + } } } } - - amalgamatedDuplicates = new Map(); - - // Initialize global symbol table - let augmentations: (readonly (StringLiteral | Identifier)[])[] | undefined; - for (const file of host.getSourceFiles()) { - if (file.redirectInfo) { - continue; + } + function getDerivationDebugDiagnostic(location: CallExpression, derivation: Derivation): Diagnostic { + const sourceFile = getSourceFileOfNode(location) + switch(derivation._tag) { + case "EmptyObjectDerivation": { + return createError( + location.arguments[0], + Diagnostics.Deriving_type_0_1, + typeToString(derivation.type), + "intrinsically as it is an empty object" + ) } - 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?.declarations) { - 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!); + case "InvalidDerivation": { + throw new Error("Bug, derivation debug called with InvalidDerivation") } - if (file.jsGlobalAugmentations) { - mergeSymbolTable(globals, file.jsGlobalAugmentations); + case "FromBlockScope": { + return createError( + location.arguments[0], + Diagnostics.Deriving_type_0_1, + typeToString(derivation.type), + `using block-scoped implicit ${derivation.implicit.symbol.escapedName}` + ); } - if (file.patternAmbientModules && file.patternAmbientModules.length) { - patternAmbientModules = concatenate(patternAmbientModules, file.patternAmbientModules); + case "FromImplicitScope": { + return createError( + location.arguments[0], + Diagnostics.Deriving_type_0_1, + typeToString(derivation.type), + `using implicit ${ + getSourceFileOfNode(location).fileName !== getSourceFileOfNode(derivation.implicit).fileName ? + `${getImportPath(derivation.implicit)}#${derivation.implicit.symbol.escapedName}` : + derivation.implicit.symbol.escapedName + }` + ) } - if (file.moduleAugmentations.length) { - (augmentations || (augmentations = [])).push(file.moduleAugmentations); + case "FromIntersectionStructure": { + return createDiagnosticForNodeFromMessageChain( + sourceFile, + location.arguments[0], + chainDiagnosticMessages( + derivation.fields.map((d) => createDiagnosticMessageChainFromDiagnostic(getDerivationDebugDiagnostic(location, d))), + Diagnostics.Deriving_type_0_1, + typeToString(derivation.type), + "intrinsically as an intersection" + ) + ) } - 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); - } - }); + case "FromObjectStructure": { + return createDiagnosticForNodeFromMessageChain( + sourceFile, + location.arguments[0], + chainDiagnosticMessages( + derivation.fields.map((d) => createDiagnosticMessageChainFromDiagnostic(getDerivationDebugDiagnostic(location, d.value))), + Diagnostics.Deriving_type_0_1, + typeToString(derivation.type), + "intrinsically as an object structure" + ) + ) + } + case "FromTupleStructure": { + return createDiagnosticForNodeFromMessageChain( + sourceFile, + location.arguments[0], + chainDiagnosticMessages( + derivation.fields.map((d) => createDiagnosticMessageChainFromDiagnostic(getDerivationDebugDiagnostic(location, d))), + Diagnostics.Deriving_type_0_1, + typeToString(derivation.type), + "intrinsically as a tuple structure" + ) + ) + } + case "FromLiteral": { + return createError( + location.arguments[0], + Diagnostics.Deriving_type_0_1, + typeToString(derivation.type), + `intrinsically as a literal ${typeof derivation.value}` + ) + } + case "FromPriorDerivation": { + return createError( + location.arguments[0], + Diagnostics.Deriving_type_0_1, + typeToString(derivation.type), + `from derivation scope` + ) + } + case "FromRule": { + return createDiagnosticForNodeFromMessageChain( + sourceFile, + location.arguments[0], + chainDiagnosticMessages( + derivation.arguments.map((d) => createDiagnosticMessageChainFromDiagnostic(getDerivationDebugDiagnostic(location, d))), + Diagnostics.Deriving_type_0_1, + typeToString(derivation.type), + `using${(derivation.usedBy.length > 0 ? " (recursive)" : "")} rule ${ + getSourceFileOfNode(location).fileName !== getSourceFileOfNode(derivation.rule).fileName ? + `${getImportPath(derivation.rule)}#${derivation.rule.symbol.escapedName}` : + derivation.rule.symbol.escapedName + }` + ) + ) + } + default: { + // @ts-expect-error + derivation._tag } + throw new Error("Bug, unreachable") } - - // 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); - } + } + function getImportPath(declaration: Declaration) { + let path: string | undefined; + const locationTag = getAllJSDocTags(declaration, (tag): tag is JSDocTag => tag.tagName.escapedText === "tsplus" && tag.comment?.toString().startsWith("location") === true)[0]; + if (locationTag) { + const match = locationTag.comment!.toString().match(/^location "(.*)"/); + if (match) { + path = match[1] } } - - // 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(/*symbol*/ undefined, emptySymbols, emptyArray, emptyArray, emptyArray); + if (!path) { + path = getImportLocation(fileMap.map, getSourceFileOfNode(declaration).fileName); } - - globalReadonlyArrayType = getGlobalTypeOrUndefined("ReadonlyArray" as __String, /*arity*/ 1) as GenericType || globalArrayType; - anyReadonlyArrayType = globalReadonlyArrayType ? createTypeFromGenericGlobalType(globalReadonlyArrayType, [anyType]) : anyArrayType; - globalThisType = getGlobalTypeOrUndefined("ThisType" as __String, /*arity*/ 1) as GenericType; - - 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); - } + return path; + } + function deriveParameter(deriveCallNode: CallLikeExpression, type: Type, parameterIndex: number): Type { + const nodeLinks = getNodeLinks(deriveCallNode); + if (!nodeLinks.tsPlusParameterDerivations) { + nodeLinks.tsPlusParameterDerivations = new Map(); + } + if (!nodeLinks.tsPlusParameterDerivations!.has(parameterIndex)) { + const derivationDiagnostics: Diagnostic[] = []; + const derivation = deriveTypeWorker(deriveCallNode, type, type, derivationDiagnostics, [], [], []); + nodeLinks.tsPlusParameterDerivations!.set(parameterIndex, derivation); + if (isErrorType(derivation.type)) { + derivationDiagnostics.forEach((diagnostic) => { + diagnostics.add(diagnostic); + }) + return errorType; } } - - 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); + return nodeLinks.tsPlusParameterDerivations.get(parameterIndex)!.type; + } + function deriveType(deriveCallNode: CallExpression, type: Type): Type { + const nodeLinks = getNodeLinks(deriveCallNode); + if (!nodeLinks.tsPlusDerivation) { + const derivationDiagnostics: Diagnostic[] = []; + const derivation = deriveTypeWorker(deriveCallNode, type, type, derivationDiagnostics, [], [], []); + nodeLinks.tsPlusDerivation = derivation; + if (isErrorType(derivation.type)) { + derivationDiagnostics.forEach((diagnostic) => { + diagnostics.add(diagnostic); + }) + return errorType; + } else if (deriveCallNode.arguments.length > 0) { + diagnostics.add(getDerivationDebugDiagnostic(deriveCallNode, nodeLinks.tsPlusDerivation)); + return errorType; + } + } + return nodeLinks.tsPlusDerivation.type; + } + function getSelfExportStatement(location: Node) { + let current = location; + while (!isVariableStatement(current) && current.parent) { + current = current.parent; + } + if (current.parent) { + return current; + } + } + function isLocalImplicit(node: Node): boolean { + return getAllJSDocTags(node, (tag): tag is JSDocTag => tag.tagName.escapedText === "tsplus" && typeof tag.comment === 'string' && tag.comment === 'implicit local').length > 0; + } + function tsPlusFlattenTags(toFlat: string[][]): readonly string[] { + let strings: readonly string[] = toFlat[0]; + for (let i = 1; i < toFlat.length; i++) { + strings = flatMap(strings, (prefix) => toFlat[i].map((post) => prefix + "," + post)) + } + return strings.map((s) => `[${s}]`); + } + function collectTypeTagsOfType(type: Type) { + return flatMap(collectRelevantSymbols(type), (s) => typeSymbolCache.get(s)); + } + function getTsPlusDerivationRules(symbol: Symbol, targetTypes: readonly Type[]) { + const mergedRules: { lazyRule: Declaration | undefined; rules: [Rule, number, Declaration, Set][]; } = { + lazyRule: void 0, + rules: [] + } + const paramExtensions = tsPlusFlattenTags(map(targetTypes, (t) => ["_", ...collectTypeTagsOfType(t)])); + const dedupe = new Set(); + forEach(map(symbol.declarations, collectTsPlusTypeTags), (tags) => { + forEach(tags, (tag) => { + collectForTag(tag); + paramExtensions.forEach((extended) => collectForTag(tag + extended)); + }); + }); + mergedRules.rules = mergedRules.rules.sort((a, b) => a[1] - b[1]); + return mergedRules; + function collectForTag(tag: string) { + const foundRules = tsPlusWorldScope.rules.get(tag); + if (foundRules) { + if (foundRules.lazyRule) { + mergedRules.lazyRule = foundRules.lazyRule; + } + foundRules.rules.forEach((rule) => { + if (!dedupe.has(rule[2])) { + dedupe.add(rule[2]); + mergedRules.rules.push(rule); } }); } - 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; - tsPlusDebug && console.time("initTsPlusTypeChecker") - initTsPlusTypeChecker(); - tsPlusDebug && console.timeEnd("initTsPlusTypeChecker") + } } - 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) { - for (const name of getHelperNames(helper)) { - if (requestedExternalEmitHelperNames.has(name)) continue; - requestedExternalEmitHelperNames.add(name); - - 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); - } - else if (helper & ExternalEmitHelpers.ClassPrivateFieldGet) { - if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 3)) { - error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 4); - } - } - else if (helper & ExternalEmitHelpers.ClassPrivateFieldSet) { - if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 4)) { - error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 5); - } - } - else if (helper & ExternalEmitHelpers.SpreadArray) { - if (!some(getSignaturesOfSymbol(symbol), signature => getParameterCount(signature) > 2)) { - error(location, Diagnostics.This_syntax_requires_an_imported_helper_named_1_with_2_parameters_which_is_not_compatible_with_the_one_in_0_Consider_upgrading_your_version_of_0, externalHelpersModuleNameText, name, 3); - } - } - } - } - } + function shouldTreatNominally(type: Type) { + if (type.symbol && type.symbol.declarations) { + for (const declaration of type.symbol.declarations) { + if ((isInterfaceDeclaration(declaration) || isClassDeclaration(declaration)) && declaration.tsPlusDeriveTags && declaration.tsPlusDeriveTags.length > 0) { + return true; } - requestedExternalEmitHelpers |= helpers; } } + return false; } - function getHelperNames(helper: ExternalEmitHelpers) { - switch (helper) { - case ExternalEmitHelpers.Extends: return ["__extends"]; - case ExternalEmitHelpers.Assign: return ["__assign"]; - case ExternalEmitHelpers.Rest: return ["__rest"]; - case ExternalEmitHelpers.Decorate: return legacyDecorators ? ["__decorate"] : ["__esDecorate", "__runInitializers"]; - 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.SpreadArray: return ["__spreadArray"]; - 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.ClassPrivateFieldIn: return ["__classPrivateFieldIn"]; - case ExternalEmitHelpers.CreateBinding: return ["__createBinding"]; - case ExternalEmitHelpers.SetFunctionName: return ["__setFunctionName"]; - case ExternalEmitHelpers.PropKey: return ["__propKey"]; - default: return Debug.fail("Unrecognized helper"); - } + function getImplicitTags(type: Type): Set { + const tags = new Set(); + collectImplicitTagsWorker(type, tags, undefined); + return tags; } - 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; + function collectImplicitTagsWorker(type: Type, tags: Set, prefix: string | undefined) { + if (type.flags & TypeFlags.UnionOrIntersection) { + forEach((type as UnionOrIntersectionType).types, (d) => { + collectImplicitTagsWorker(d, tags, prefix); + }) } - return externalHelpersModule; - } - - // GRAMMAR CHECKING - - function checkGrammarModifiers(node: HasModifiers | HasDecorators | HasIllegalModifiers | HasIllegalDecorators): boolean { - const quickResult = reportObviousDecoratorErrors(node) || reportObviousModifierErrors(node); - if (quickResult !== undefined) { - return quickResult; + else if (shouldTreatNominally(type)) { + if ((getObjectFlags(type) & ObjectFlags.Reference)) { + const name = symbolName(type.symbol); + const args = getTypeArguments(type as TypeReference); + forEach(args, (arg) => { + collectImplicitTagsWorker(arg, tags, prefix ? `${prefix}.${name}` : name) + }) + } else { + tags.add(prefix ? `${prefix}.${symbolName(type.symbol)}` : symbolName(type.symbol)); + } } - - if (isParameter(node) && parameterIsThisKeyword(node)) { - return grammarErrorOnFirstToken(node, Diagnostics.Neither_decorators_nor_modifiers_may_be_applied_to_this_parameters); + else if (type.flags & (TypeFlags.Intrinsic | TypeFlags.StringLiteral | TypeFlags.NumberLiteral | TypeFlags.TypeParameter)) { + tags.add(prefix ? `${prefix}.${typeToString(type)}` : typeToString(type)); + } + else { + const props = getPropertiesOfType(type); + forEach(props, (prop) => { + tags.add(prefix ? `${prefix}.${symbolName(prop)}` : symbolName(prop)); + }) + if (props.length === 0) { + tags.add(prefix ? `${prefix}._` : `_`); + } } + } - let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastOverride: Node | undefined, firstDecorator: Decorator | undefined; - let flags = ModifierFlags.None; - let sawExportBeforeDecorators = false; - // We parse decorators and modifiers in four contiguous chunks: - // [...leadingDecorators, ...leadingModifiers, ...trailingDecorators, ...trailingModifiers]. It is an error to - // have both leading and trailing decorators. - let hasLeadingDecorators = false; - for (const modifier of (node as HasModifiers).modifiers!) { - if (isDecorator(modifier)) { - if (!nodeCanBeDecorated(legacyDecorators, 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 (legacyDecorators && (node.kind === SyntaxKind.GetAccessor || node.kind === SyntaxKind.SetAccessor)) { - const accessors = getAllAccessorDeclarations((node.parent as ClassDeclaration).members, node as AccessorDeclaration); - if (hasDecorators(accessors.firstAccessor) && node === accessors.secondAccessor) { - return grammarErrorOnFirstToken(node, Diagnostics.Decorators_cannot_be_applied_to_multiple_get_Slashset_accessors_of_the_same_name); - } - } + function getTypeAndImplicitTags(symbol: Symbol) { + const links = getSymbolLinks(symbol); + if (!links.tsPlusTypeAndImplicitTags) { + const type = getTypeOfSymbol(symbol); + const tags = getImplicitTags(type); + links.tsPlusTypeAndImplicitTags = { + type, + tags + } + } + return links.tsPlusTypeAndImplicitTags; + } - // if we've seen any modifiers aside from `export`, `default`, or another decorator, then this is an invalid position - if (flags & ~(ModifierFlags.ExportDefault | ModifierFlags.Decorator)) { - return grammarErrorOnNode(modifier, Diagnostics.Decorators_are_not_valid_here); - } + function indexInScope(entry: Symbol) { + const { tags } = getTypeAndImplicitTags(entry); + forEach(arrayFrom(tags.values()), (tag) => { + let index = tsPlusWorldScope.implicits.get(tag); + if (!index) { + index = new Set(); + tsPlusWorldScope.implicits.set(tag, index); + } + index.add(entry); + }); + } - // if we've already seen leading decorators and leading modifiers, then trailing decorators are an invalid position - if (hasLeadingDecorators && flags & ModifierFlags.Modifier) { - Debug.assertIsDefined(firstDecorator); - const sourceFile = getSourceFileOfNode(modifier); - if (!hasParseDiagnostics(sourceFile)) { - addRelatedInfo( - error(modifier, Diagnostics.Decorators_may_not_appear_after_export_or_export_default_if_they_also_appear_before_export), - createDiagnosticForNode(firstDecorator, Diagnostics.Decorator_used_before_export_here)); - return true; + function lookupInGlobalScope(location: Node, type: Type, selfExport: Node | undefined, tags: readonly string[]): Derivation | undefined { + let candidates: Set | undefined; + for (const tag of tags) { + const maybeBetter = tsPlusWorldScope.implicits.get(tag); + if (!maybeBetter) { + return + } + if (!candidates || maybeBetter.size < candidates.size) { + candidates = maybeBetter; + } + } + const candidatesIterator = candidates?.values(); + if (!candidatesIterator) { + return + } + let current = candidatesIterator.next(); + while (!current.done) { + const candidate = current.value; + if (tags.every((tag) => tsPlusWorldScope.implicits.get(tag)?.has(candidate) === true)) { + if (getSelfExportStatement(candidate.valueDeclaration!) !== selfExport && + isBlockScopedNameDeclaredBeforeUse(candidate.valueDeclaration!, location)) { + const { type: implicitType } = getTypeAndImplicitTags(candidate); + if (isTypeAssignableTo(implicitType, type)) { + return { + _tag: "FromImplicitScope", + type, + implicit: candidate.valueDeclaration! + }; } - return false; } + } + current = candidatesIterator.next(); + } + } - flags |= ModifierFlags.Decorator; - - // if we have not yet seen a modifier, then these are leading decorators - if (!(flags & ModifierFlags.Modifier)) { - hasLeadingDecorators = true; - } - else if (flags & ModifierFlags.Export) { - sawExportBeforeDecorators = true; + function lookupInBlockScope(location: Node, type: Type, tags: readonly string[]): Derivation | undefined { + let container = getEnclosingBlockScopeContainer(location) as LocalsContainer; + while (container) { + if (container.locals) { + for (const local of arrayFrom(container.locals.values())) { + if (local.valueDeclaration && + (isParameterDeclaration(local.valueDeclaration as VariableLikeDeclaration) || isLocalImplicit(local.valueDeclaration)) && + isNamedDeclaration(local.valueDeclaration) && + isIdentifier(local.valueDeclaration.name) && + isBlockScopedNameDeclaredBeforeUse(local.valueDeclaration.name, location) + ) { + const { tags: implicitTags, type: implicitType } = getTypeAndImplicitTags(local); + if (tags.every((tag) => implicitTags.has(tag))) { + if (isTypeAssignableTo(implicitType, type)) { + local.valueDeclaration.symbol.isReferenced = SymbolFlags.Value; + const blockLinks = getNodeLinks(container); + if (!blockLinks.uniqueNames) { + blockLinks.uniqueNames = new Set() + } + const declaration = local.valueDeclaration as NamedDeclaration & { name: Identifier }; + blockLinks.uniqueNames.add(declaration); + const implicitLinks = getNodeLinks(local.valueDeclaration); + implicitLinks.needsUniqueNameInSope = true; + return { + _tag: "FromBlockScope", + type, + implicit: declaration + }; + } + } + } } + } + container = getEnclosingBlockScopeContainer(container) as LocalsContainer; + } + } - firstDecorator ??= modifier; + function deriveTypeWorker( + location: Node, + originalType: Type, + type: Type, + diagnostics: Diagnostic[], + derivationScope: FromRule[], + prohibited: Type[], + currentDerivation: Type[] + ): Derivation { + const sourceFile = getSourceFileOfNode(location) + if (isTypeIdenticalTo(type, emptyObjectType)) { + return { + _tag: "EmptyObjectDerivation", + type + }; + } + for (const derivedType of derivationScope) { + if (isTypeAssignableTo(derivedType.type, type)) { + const rule: FromPriorDerivation = { + _tag: "FromPriorDerivation", + derivation: derivedType, + type + }; + derivedType.usedBy.push(rule); + return rule; } - else { - 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 && (modifier.kind !== SyntaxKind.StaticKeyword || !isClassLike(node.parent))) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_an_index_signature, tokenToString(modifier.kind)); + } + const tagsForLookup = arrayFrom(getImplicitTags(type).values()); + const inBlockdScope = lookupInBlockScope(location, type, tagsForLookup); + if (inBlockdScope) { + return inBlockdScope; + } + const selfExport = getSelfExportStatement(location); + const inWorldScope = lookupInGlobalScope(location, type, selfExport, tagsForLookup); + if (inWorldScope) { + return inWorldScope; + } + const newCurrentDerivation = [...currentDerivation, type]; + for (const prohibitedType of prohibited) { + if (isTypeIdenticalTo(prohibitedType, type)) { + if (diagnostics.length === 0) { + const digs: Diagnostic[] = []; + for (let i = 1; i < newCurrentDerivation.length - 1; i++) { + const step = newCurrentDerivation[i]; + digs.push(createError( + location, + Diagnostics.Failed_derivation_of_type_0, + typeToString(step) + )); } + const diagnostic = createDiagnosticForNodeFromMessageChain( + sourceFile, + location, + chainDiagnosticMessages( + map(digs, createDiagnosticMessageChainFromDiagnostic), + Diagnostics.Cannot_derive_type_0_the_derivation_requires_a_lazy_rule_to_be_in_scope_as_it_is_cyclic_for_the_type_1, + typeToString(newCurrentDerivation[0]), + typeToString(newCurrentDerivation[newCurrentDerivation.length - 1]) + ) + ) + diagnostics.push(diagnostic) } - if (modifier.kind !== SyntaxKind.InKeyword && modifier.kind !== SyntaxKind.OutKeyword && modifier.kind !== SyntaxKind.ConstKeyword) { - if (node.kind === SyntaxKind.TypeParameter) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_type_parameter, tokenToString(modifier.kind)); - } + return { + _tag: "InvalidDerivation", + type: errorType + }; + } + } + let hasRules = false; + if ((type.flags & TypeFlags.Object) && ((type as ObjectType).objectFlags & ObjectFlags.Reference) && !isTupleType(type) && type.symbol && type.symbol.declarations) { + const targetTypes = getTypeArguments(type as TypeReference); + const mergedRules = getTsPlusDerivationRules(type.symbol, targetTypes); + ruleCheck: for (const [rule, _, ruleDeclaration, options] of mergedRules.rules) { + const toCheck: Type[] = []; + if (rule.paramActions.length !== targetTypes.length) { + continue ruleCheck; } - switch (modifier.kind) { - case SyntaxKind.ConstKeyword: - if (node.kind !== SyntaxKind.EnumDeclaration && node.kind !== SyntaxKind.TypeParameter) { - return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword)); - } - const parent = node.parent; - if (node.kind === SyntaxKind.TypeParameter && !(isFunctionLikeDeclaration(parent) || isClassLike(parent) || isFunctionTypeNode(parent) || - isConstructorTypeNode(parent) || isCallSignatureDeclaration(parent) || isConstructSignatureDeclaration(parent) || isMethodSignature(parent))) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_can_only_appear_on_a_type_parameter_of_a_function_method_or_class, tokenToString(modifier.kind)); - } - break; - case SyntaxKind.OverrideKeyword: - // If node.kind === SyntaxKind.Parameter, checkParameter reports an error if it's not a parameter property. - if (flags & ModifierFlags.Override) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "override"); - } - else if (flags & ModifierFlags.Ambient) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "override", "declare"); - } - else if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "override", "readonly"); - } - else if (flags & ModifierFlags.Accessor) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "override", "accessor"); - } - else if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "override", "async"); - } - flags |= ModifierFlags.Override; - lastOverride = modifier; - 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.Override) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "override"); - } - else if (flags & ModifierFlags.Static) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "static"); - } - else if (flags & ModifierFlags.Accessor) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "accessor"); - } - 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 (isPrivateIdentifierClassElementDeclaration(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 (flags & ModifierFlags.Accessor) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "accessor"); - } - 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 (flags & ModifierFlags.Override) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "override"); - } - flags |= ModifierFlags.Static; - lastStatic = modifier; - break; - - case SyntaxKind.AccessorKeyword: - if (flags & ModifierFlags.Accessor) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "accessor"); - } - else if (flags & ModifierFlags.Readonly) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "accessor", "readonly"); - } - else if (flags & ModifierFlags.Ambient) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "accessor", "declare"); - } - else if (node.kind !== SyntaxKind.PropertyDeclaration) { - return grammarErrorOnNode(modifier, Diagnostics.accessor_modifier_can_only_appear_on_a_property_declaration); - } - - flags |= ModifierFlags.Accessor; - 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 reports 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); - } - else if (flags & ModifierFlags.Accessor) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "readonly", "accessor"); - } - flags |= ModifierFlags.Readonly; - break; - - case SyntaxKind.ExportKeyword: - if (compilerOptions.verbatimModuleSyntax && - !(node.flags & NodeFlags.Ambient) && - node.kind !== SyntaxKind.TypeAliasDeclaration && - node.kind !== SyntaxKind.InterfaceDeclaration && - // ModuleDeclaration needs to be checked that it is uninstantiated later - node.kind !== SyntaxKind.ModuleDeclaration && - node.parent.kind === SyntaxKind.SourceFile && - (moduleKind === ModuleKind.CommonJS || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) - ) { - return grammarErrorOnNode(modifier, Diagnostics.A_top_level_export_modifier_cannot_be_used_on_value_declarations_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); - } - 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); - } - else if (!(flags & ModifierFlags.Export)) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "export", "default"); - } - else if (sawExportBeforeDecorators) { - return grammarErrorOnNode(firstDecorator!, Diagnostics.Decorators_are_not_valid_here); - } - - 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 (flags & ModifierFlags.Override) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "override"); - } - 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 (isPrivateIdentifierClassElementDeclaration(node)) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_a_private_identifier, "declare"); + let index = 0; + for (const paramAction of rule.paramActions) { + if (paramAction === "_") { + toCheck.push(targetTypes[index]) + } else if (paramAction === "[]") { + if (isTupleType(targetTypes[index])) { + toCheck.push(targetTypes[index]) + } else { + continue ruleCheck; } - else if (flags & ModifierFlags.Accessor) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "declare", "accessor"); + } else if (paramAction === "|") { + if (targetTypes[index].flags & TypeFlags.Union) { + const typesInUnion = (targetTypes[index] as UnionType).types; + const typesInUnionTuple = createTupleType(typesInUnion, void 0, false, void 0); + toCheck.push(typesInUnionTuple) + } else { + continue ruleCheck; } - flags |= ModifierFlags.Ambient; - lastDeclare = modifier; - break; - - case SyntaxKind.AbstractKeyword: - if (flags & ModifierFlags.Abstract) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "abstract"); + } else if (paramAction === "&") { + if (targetTypes[index].flags & TypeFlags.Intersection) { + const typesInUnion = (targetTypes[index] as IntersectionType).types; + const typesInUnionTuple = createTupleType(typesInUnion, void 0, false, void 0); + toCheck.push(typesInUnionTuple) + } else { + continue ruleCheck; } - if (node.kind !== SyntaxKind.ClassDeclaration && - node.kind !== SyntaxKind.ConstructorType) { - 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))) { - const message = node.kind === SyntaxKind.PropertyDeclaration - ? Diagnostics.Abstract_properties_can_only_appear_within_an_abstract_class - : Diagnostics.Abstract_methods_can_only_appear_within_an_abstract_class; - return grammarErrorOnNode(modifier, message); - } - 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 (flags & ModifierFlags.Override) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "abstract", "override"); - } - if (flags & ModifierFlags.Accessor) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "abstract", "accessor"); + } else { + continue ruleCheck; + } + index++; + } + if (toCheck.length !== targetTypes.length) { + continue ruleCheck; + } + const signatures = getSignaturesOfType(getTypeOfNode(ruleDeclaration), SignatureKind.Call); + for (const signature of signatures) { + if (signature.typeParameters && signature.typeParameters.length === toCheck.length) { + const mapper = createTypeMapper(signature.typeParameters, toCheck); + if (every(signature.typeParameters, (param, i) => { + const constraint = instantiateType(getConstraintOfTypeParameter(param), mapper); + if (!constraint) { + return true; + } + return isTypeAssignableTo(toCheck[i], constraint); + })) { + const instantiated = getSignatureInstantiation(signature, toCheck, false); + if (instantiated.parameters.length === 1 && + instantiated.parameters[0].valueDeclaration && + (instantiated.parameters[0].valueDeclaration as ParameterDeclaration).dotDotDotToken + ) { + const residualType = getTypeOfSymbol(instantiated.parameters[0]); + if (isTupleType(residualType)) { + const types = getTypeArguments(residualType); + const selfRule: FromRule = { + _tag: "FromRule", + type, + rule: ruleDeclaration, + arguments: [], + usedBy: [], + lazyRule: mergedRules.lazyRule + }; + const supportsLazy = !!mergedRules.lazyRule && !options.has("no-recursion"); + const newProhibited = !supportsLazy ? [...prohibited, type] : prohibited; + const newDerivationScope: FromRule[] = supportsLazy ? [...derivationScope, selfRule] : derivationScope; + const derivations = map(types, (childType) => deriveTypeWorker( + location, + originalType, + childType, + diagnostics, + newDerivationScope, + newProhibited, + newCurrentDerivation + )); + if (!find(derivations, (d) => isErrorType(d.type))) { + selfRule.arguments.push(...derivations); + return selfRule; + } + } } } - 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; - - case SyntaxKind.InKeyword: - case SyntaxKind.OutKeyword: - const inOutFlag = modifier.kind === SyntaxKind.InKeyword ? ModifierFlags.In : ModifierFlags.Out; - const inOutText = modifier.kind === SyntaxKind.InKeyword ? "in" : "out"; - if (node.kind !== SyntaxKind.TypeParameter || !(isInterfaceDeclaration(node.parent) || isClassLike(node.parent) || isTypeAliasDeclaration(node.parent))) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_can_only_appear_on_a_type_parameter_of_a_class_interface_or_type_alias, inOutText); - } - if (flags & inOutFlag) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, inOutText); - } - if (inOutFlag & ModifierFlags.In && flags & ModifierFlags.Out) { - return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "in", "out"); - } - flags |= inOutFlag; - break; + } + } + if (diagnostics.length > 0) { + break ruleCheck; } } } - - 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.Override) { - return grammarErrorOnNode(lastOverride!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "override"); // TODO: GH#18217 + if (diagnostics.length === 0 && !hasRules && isTupleType(type)) { + const props = getTypeArguments(type); + const derivations = map(props, (prop) => deriveTypeWorker( + location, + originalType, + prop, + diagnostics, + derivationScope, + prohibited, + newCurrentDerivation + )); + if (!find(derivations, (d) => isErrorType(d.type))) { + return { + _tag: "FromTupleStructure", + type, + fields: derivations + }; } - if (flags & ModifierFlags.Async) { - return grammarErrorOnNode(lastAsync!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async"); + } + if (diagnostics.length === 0 && !hasRules && (type.flags & TypeFlags.Intersection)) { + const props = (type as IntersectionType).types; + const canIntersect = !find(props, (p) => { + if ((p.flags & TypeFlags.Object) && getSignaturesOfType(p, SignatureKind.Call).length === 0 && getSignaturesOfType(p, SignatureKind.Construct).length === 0 && !isArrayOrTupleLikeType(p)) { + return false + } + return true + }) + if (canIntersect) { + const derivations = map(props, (prop) => deriveTypeWorker( + location, + originalType, + prop, + diagnostics, + derivationScope, + prohibited, + newCurrentDerivation + )); + if (!find(derivations, (d) => isErrorType(d.type))) { + return { + _tag: "FromIntersectionStructure", + type, + fields: derivations + }; + } } - 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"); + if (diagnostics.length === 0 && !hasRules && (type.flags & TypeFlags.Object)) { + const call = getSignaturesOfType(type, SignatureKind.Call); + const construct = getSignaturesOfType(type, SignatureKind.Construct); + if (call.length === 0 && construct.length === 0) { + const props = getPropertiesOfType(type); + if (!find(props, (p) => p.escapedName.toString().startsWith("__@"))) { + const fields: FromObjectStructure["fields"] = []; + for (const prop of props) { + fields.push({ + prop, + value: deriveTypeWorker( + location, + originalType, + getTypeOfSymbol(prop), + diagnostics, + derivationScope, + prohibited, + newCurrentDerivation + ) + }) + } + if (!find(fields, (d) => isErrorType(d.value.type))) { + return { + _tag: "FromObjectStructure", + type, + fields + }; + } + } + } } - 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); + if (diagnostics.length === 0 && !hasRules && (type.flags & (TypeFlags.StringLiteral | TypeFlags.NumberLiteral))) { + return { + _tag: "FromLiteral", + type, + value: (type as StringLiteralType | NumberLiteralType).value + }; } - 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 (diagnostics.length === 0) { + if (isTypeIdenticalTo(originalType, type)) { + diagnostics.push(createError(location, Diagnostics.Cannot_derive_type_0_and_no_derivation_rules_are_found, typeToString(originalType))); + } else { + const digs: Diagnostic[] = []; + for (let i = 1; i < newCurrentDerivation.length - 1; i++) { + const step = newCurrentDerivation[i]; + digs.push(createError( + location, + Diagnostics.Failed_derivation_of_type_0, + typeToString(step) + )); + } + const diagnostic = createDiagnosticForNodeFromMessageChain( + sourceFile, + location, + chainDiagnosticMessages( + map(digs, createDiagnosticMessageChainFromDiagnostic), + Diagnostics.Cannot_derive_type_0_you_may_want_to_try_add_an_implicit_for_1_in_scope, + typeToString(newCurrentDerivation[0]), + typeToString(newCurrentDerivation[newCurrentDerivation.length - 1]) + ) + ) + diagnostics.push(diagnostic) + } } - if (flags & ModifierFlags.Async) { - return checkGrammarAsyncModifier(node, lastAsync!); + return { + _tag: "InvalidDerivation", + type: errorType + }; + } + function tsPlusDoGetBindOutput(node: CallExpression, resultType: Type, statementNode: Statement) { + const mapFluent = getFluentExtension(resultType, "map"); + if (mapFluent) { + for (const signature of getSignaturesOfType(mapFluent, SignatureKind.Call)) { + const original = (signature as TsPlusSignature).tsPlusOriginal; + if (original.minArgumentCount === 2 && original.typeParameters) { + const context = createInferenceContext(original.typeParameters, original, InferenceFlags.None); + inferTypes(context.inferences, resultType, getTypeOfSymbol(original.parameters[0])); + const instantiated = getSignatureInstantiation(original, getInferredTypes(context), /* isJavascript */ false); + if (isTypeAssignableTo(resultType, getTypeOfSymbol(instantiated.parameters[0]))) { + const funcType = getTypeOfSymbol(instantiated.parameters[1]); + const statementLinks = getNodeLinks(statementNode); + statementLinks.tsPlusDoBindType = [node, resultType]; + return getTypeOfSymbol(getSignaturesOfType(funcType, SignatureKind.Call)[0].parameters[0]); + } + } + } } - return false; + diagnostics.add(error(node, Diagnostics.Cannot_find_a_valid_flatMap_extension_for_type_0, typeToString(resultType))); + return errorType; } - - /** - * true | false: Early return this value from checkGrammarModifiers. - * undefined: Need to do full checking on the modifiers. - */ - function reportObviousModifierErrors(node: HasModifiers | HasIllegalModifiers): boolean | undefined { - if (!node.modifiers) return false; - - const modifier = findFirstIllegalModifier(node); - return modifier && grammarErrorOnFirstToken(modifier, Diagnostics.Modifiers_cannot_appear_here); + function tsPlusDoCheckBind(node: CallExpression, resultType: Type) { + const links = getNodeLinks(node); + if (!links.tsPlusResolvedType) { + links.tsPlusResolvedType = tsPlusDoCheckBindWorker(node, resultType); + } + return links.tsPlusResolvedType; } - - function findFirstModifierExcept(node: HasModifiers, allowedModifier: SyntaxKind): Modifier | undefined { - const modifier = find(node.modifiers, isModifier); - return modifier && modifier.kind !== allowedModifier ? modifier : undefined; + function tsPlusDoCheckBindWorker(node: CallExpression, resultType: Type) { + if ( + isVariableDeclaration(node.parent) && + isVariableDeclarationList(node.parent.parent) && + (node.parent.parent.flags & NodeFlags.Const) && + isVariableStatement(node.parent.parent.parent) && + isBlock(node.parent.parent.parent.parent) && + isArrowFunction(node.parent.parent.parent.parent.parent) && + isCallExpression(node.parent.parent.parent.parent.parent.parent) + ) { + const callExp = node.parent.parent.parent.parent.parent.parent as CallExpression; + const links = getNodeLinks(callExp); + if (links.isTsPlusDoCall) { + return tsPlusDoGetBindOutput(node, resultType, node.parent.parent.parent); + } } - - function findFirstIllegalModifier(node: HasModifiers | HasIllegalModifiers): Modifier | undefined { - 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: - case SyntaxKind.TypeParameter: - return undefined; - case SyntaxKind.ClassStaticBlockDeclaration: - case SyntaxKind.PropertyAssignment: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.NamespaceExportDeclaration: - case SyntaxKind.MissingDeclaration: - return find(node.modifiers, isModifier); - default: - if (node.parent.kind === SyntaxKind.ModuleBlock || node.parent.kind === SyntaxKind.SourceFile) { - return undefined; - } - switch (node.kind) { - case SyntaxKind.FunctionDeclaration: - return findFirstModifierExcept(node, SyntaxKind.AsyncKeyword); - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ConstructorType: - return findFirstModifierExcept(node, SyntaxKind.AbstractKeyword); - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.VariableStatement: - case SyntaxKind.TypeAliasDeclaration: - return find(node.modifiers, isModifier); - case SyntaxKind.EnumDeclaration: - return findFirstModifierExcept(node, SyntaxKind.ConstKeyword); - default: - Debug.assertNever(node); - } + else if ( + isExpressionStatement(node.parent) && + isBlock(node.parent.parent) && + isArrowFunction(node.parent.parent.parent) && + isCallExpression(node.parent.parent.parent.parent) + ) { + const callExp = node.parent.parent.parent.parent as CallExpression; + const links = getNodeLinks(callExp); + if (links.isTsPlusDoCall) { + return tsPlusDoGetBindOutput(node, resultType, node.parent); + } } + else if ( + isReturnStatement(node.parent) && + isBlock(node.parent.parent) && + isArrowFunction(node.parent.parent.parent) && + isCallExpression(node.parent.parent.parent.parent) + ) { + const callExp = node.parent.parent.parent.parent as CallExpression; + const links = getNodeLinks(callExp); + if (links.isTsPlusDoCall) { + links.isTsPlusDoReturnBound = true; + return tsPlusDoGetBindOutput(node, resultType, node.parent); + } + } + diagnostics.add(error(node, Diagnostics.A_call_to_bind_is_only_allowed_in_the_context_of_a_Do)); + return errorType; } - - function reportObviousDecoratorErrors(node: HasModifiers | HasDecorators | HasIllegalModifiers | HasIllegalDecorators) { - const decorator = findFirstIllegalDecorator(node); - return decorator && grammarErrorOnFirstToken(decorator, Diagnostics.Decorators_are_not_valid_here); - } - - function findFirstIllegalDecorator(node: HasModifiers | HasDecorators | HasIllegalModifiers | HasIllegalDecorators): Decorator | undefined { - return canHaveIllegalDecorators(node) ? find(node.modifiers, isDecorator) : undefined; + function tsPlusDoCheckChain(types: readonly [CallExpression, Type][], signature: Signature): Type { + let current = types[types.length - 1][1]; + for (let i = types.length - 2; i >= 0; i--) { + const [node, type] = types[i]; + const context = createInferenceContext(signature.typeParameters!, signature, InferenceFlags.None); + inferTypes(context.inferences, type, getTypeOfSymbol(signature.parameters[0])); + const childType = createAnonymousType( + void 0, + emptySymbols, + [createSignature( + void 0, + [], + void 0, + [], + current, + void 0, + 0, + SignatureFlags.None + )], + [], + [] + ); + inferTypes(context.inferences, childType, getTypeOfSymbol(signature.parameters[1])); + const instantiated = getSignatureInstantiation(signature, getInferredTypes(context), /* isJavascript */ false); + if (!isTypeAssignableTo(type, getTypeOfSymbol(instantiated.parameters[0]))) { + error(node, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(type), typeToString(getTypeOfSymbol(instantiated.parameters[0]))); + return errorType; + } + if (!isTypeAssignableTo(childType, getTypeOfSymbol(instantiated.parameters[1]))) { + error(node, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(childType), typeToString(getTypeOfSymbol(instantiated.parameters[1]))); + return errorType; + } + current = getReturnTypeOfSignature(instantiated); + if (isErrorType(current)) { + return errorType; + } + } + return current; } - - function checkGrammarAsyncModifier(node: Node, asyncModifier: Node): boolean { - switch (node.kind) { - case SyntaxKind.MethodDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return false; + function tsPlusDoCheckLastBind(node: CallExpression, mapFunction: Signature, lastBind: Type, result: Type) { + const context = createInferenceContext(mapFunction.typeParameters!, mapFunction, InferenceFlags.None); + const createdFunction = createAnonymousType( + void 0, + emptySymbols, + [createSignature( + void 0, + [], + void 0, + [], + result, + void 0, + 0, + SignatureFlags.None + )], + [], + [] + ); + inferTypes(context.inferences, lastBind, getTypeOfSymbol(mapFunction.parameters[0])); + inferTypes(context.inferences, createdFunction, getTypeOfSymbol(mapFunction.parameters[1])); + const instantiated = getSignatureInstantiation(mapFunction, getInferredTypes(context), /* isJavascript */ false); + if (!isTypeAssignableTo(lastBind, getTypeOfSymbol(instantiated.parameters[0]))) { + error(node, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(lastBind), typeToString(getTypeOfSymbol(instantiated.parameters[0]))); + return errorType; } - - return grammarErrorOnNode(asyncModifier, Diagnostics._0_modifier_cannot_be_used_here, "async"); + if (!isTypeAssignableTo(createdFunction, getTypeOfSymbol(instantiated.parameters[1]))) { + error(node, Diagnostics.Type_0_is_not_assignable_to_type_1, typeToString(createdFunction), typeToString(getTypeOfSymbol(instantiated.parameters[1]))); + return errorType; + } + return getReturnTypeOfSignature(instantiated); } - - 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); + function tsPlusDoChooseImplementation(types: [CallExpression, Type][], name: string) { + const seen = new Set(); + for (const [_, type] of types) { + const flatMap = getFluentExtension(type, name); + if (flatMap) { + for (const signature of getSignaturesOfType(flatMap, SignatureKind.Call)) { + const original = (signature as TsPlusSignature).tsPlusOriginal; + if (!seen.has(original)) { + seen.add(original); + if (original.minArgumentCount === 2 && original.typeParameters) { + const mapper = createTypeMapper(original.typeParameters, original.typeParameters.map(() => anyType)); + const p0 = instantiateType(getTypeOfSymbol(original.parameters[0]), mapper); + if (types.every((target) => isTypeAssignableTo(target[1], p0))) { + return signature as TsPlusSignature; + } + } + } + } + } } - 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); + function tsPlusDoCheckDo(node: CallExpression, checked: Type) { + const links = getNodeLinks(node); + if (!links.tsPlusResolvedType) { + links.tsPlusResolvedType = tsPlusDoCheckDoWorker(node, checked); } - return false; + return links.tsPlusResolvedType; } - - 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); + function tsPlusDoCheckDoWorker(node: CallExpression, checked: Type) { + if (isArrowFunction(node.arguments[0]) && isBlock(node.arguments[0].body)) { + const types: [CallExpression, Type][] = []; + forEach(node.arguments[0].body.statements, (statement) => { + checkSourceElement(statement); + const links = getNodeLinks(statement); + if (links.tsPlusDoBindType) { + types.push(links.tsPlusDoBindType); } + }); + if (types.length === 0) { + error(node, Diagnostics.A_Do_block_must_contain_at_least_1_bound_value); + return errorType; } - else if (isOptionalParameter(parameter)) { - seenOptionalParameter = true; - if (parameter.questionToken && parameter.initializer) { - return grammarErrorOnNode(parameter.name, Diagnostics.Parameter_cannot_have_question_mark_and_initializer); - } + getNodeLinks(node).tsPlusDoBindTypes = types; + const mapFn = tsPlusDoChooseImplementation(types, "map"); + if (!mapFn) { + error(node, Diagnostics.Cannot_find_a_valid_0_that_can_handle_all_the_types_of_1, "map", typeToString(getUnionType(types.map((t) => t[1])))) + return errorType; } - else if (seenOptionalParameter && !parameter.initializer) { - return grammarErrorOnNode(parameter.name, Diagnostics.A_required_parameter_cannot_follow_an_optional_parameter); + const flatMapFn = tsPlusDoChooseImplementation(types, "flatMap"); + if (!flatMapFn) { + error(node, Diagnostics.Cannot_find_a_valid_0_that_can_handle_all_the_types_of_1, "flatMap", typeToString(getUnionType(types.map((t) => t[1])))) + return errorType; } + getNodeLinks(node).tsPlusDoFunctions = { + flatMap: flatMapFn, + map: mapFn + }; + const lastBind = types[types.length - 1]; + const links = getNodeLinks(node); + const mapped = links.isTsPlusDoReturnBound ? lastBind[1] : tsPlusDoCheckLastBind(lastBind[0], mapFn.tsPlusOriginal, lastBind[1], checked) + const toBeFlatMapped = types.map((t, i) => i !== types.length - 1 ? t : [t[0], mapped] as typeof t); + return tsPlusDoCheckChain(toBeFlatMapped, flatMapFn.tsPlusOriginal); } + error(node, Diagnostics.The_first_argument_of_a_Do_must_be_an_arrow_function); + return errorType; } - - 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; + function checkCallExpression(node: CallExpression | NewExpression, checkMode?: CheckMode): Type { + let checked = checkCallExpressionOriginal(node, checkMode); + if (isTsPlusMacroCall(node, "Bind") && !isErrorType(checked)) { + return tsPlusDoCheckBind(node, checked); + } + if (isTsPlusMacroCall(node, "Do") && !isErrorType(checked)) { + return tsPlusDoCheckDo(node, checked); + } + if (isTsPlusMacroCall(node, "Derive") && !isErrorType(checked)) { + return deriveType(node, checked); + } + if (isTsPlusMacroCall(node, "pipeable") && !isErrorType(checked)) { + if (!isVariableDeclaration(node.parent)) { + error(node, Diagnostics.Pipeable_macro_is_only_allowed_in_variable_declarations); + return checked; + } + if (node.arguments.length > 0) { + const dataFirstSymbol = getTypeOfNode(node.arguments[0]).symbol; + if (dataFirstSymbol && dataFirstSymbol.declarations) { + const dataFirstDeclaration = find(dataFirstSymbol.declarations, (decl): decl is FunctionDeclaration | ArrowFunction | FunctionExpression => isFunctionDeclaration(decl) || isArrowFunction(decl) || isFunctionExpression(decl)) + if (dataFirstDeclaration) { + const type = generatePipeable(node.parent, dataFirstDeclaration); + if (type) { + getNodeLinks(node.parent).tsPlusDataFirstDeclaration = dataFirstDeclaration; + return type; + } + } + } + } + } + if (isTsPlusMacroCall(node, "identity") && !isErrorType(checked)) { + if (node.arguments.length === 1) { + if (isSpreadArgument(node.arguments[0])) { + error(node.arguments[0], Diagnostics.The_argument_of_an_identity_macro_must_not_be_a_spread_element); + return checked; } } } + if (isCallExpression(node)) { + tryCacheOptimizedPipeableCall(node); + } + return checked; + } + function isTsPlusImplicit(declaration: Declaration): declaration is VariableDeclaration { + if (isVariableDeclaration(declaration)) { + return declaration.isTsPlusImplicit; + } return false; } - - function checkGrammarFunctionLikeDeclaration(node: FunctionLikeDeclaration | MethodSignature): boolean { - // Prevent cascading error by short-circuit - const file = getSourceFileOfNode(node); - return checkGrammarModifiers(node) || - checkGrammarTypeParameterList(node.typeParameters, file) || - checkGrammarParameterList(node.parameters) || - checkGrammarArrowFunction(node, file) || - (isFunctionLikeDeclaration(node) && checkGrammarForUseStrictSimpleParameterList(node)); + function collectTsPlusDeriveTags(declaration: Declaration) { + if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { + return declaration.tsPlusDeriveTags || []; + } + return []; } - - function checkGrammarClassLikeDeclaration(node: ClassLikeDeclaration): boolean { - const file = getSourceFileOfNode(node); - return checkGrammarClassDeclarationHeritageClauses(node) || - checkGrammarTypeParameterList(node.typeParameters, file); + function collectTsPlusFluentTags(declaration: Declaration) { + if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { + return declaration.tsPlusFluentTags || []; + } + return []; } - - function checkGrammarArrowFunction(node: Node, file: SourceFile): boolean { - if (!isArrowFunction(node)) { - return false; + function collectTsPlusPipeableTags(declaration: Declaration) { + if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { + return declaration.tsPlusPipeableTags || []; } - - if (node.typeParameters && !(length(node.typeParameters) > 1 || node.typeParameters.hasTrailingComma || node.typeParameters[0].constraint)) { - if (file && fileExtensionIsOneOf(file.fileName, [Extension.Mts, Extension.Cts])) { - grammarErrorOnNode(node.typeParameters[0], Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Add_a_trailing_comma_or_explicit_constraint); - } + return []; + } + function collectTsPlusIndexTags(declaration: Declaration) { + if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { + return declaration.tsPlusIndexTags || []; } - - 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); + return []; } - - 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); - } + function collectTsPlusPipeableIndexTags(declaration: Declaration) { + if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { + return declaration.tsPlusPipeableIndexTags || []; } - 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); + return []; + } + function collectTsPlusTypeTags(declaration: Declaration) { + if (isInterfaceDeclaration(declaration) || isTypeAliasDeclaration(declaration) || isClassDeclaration(declaration)) { + return declaration.tsPlusTypeTags || []; } - if (hasEffectiveModifiers(parameter)) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_accessibility_modifier); + return []; + } + function collectTsPlusCompanionTags(declaration: Declaration) { + if (isClassDeclaration(declaration) || isInterfaceDeclaration(declaration) || isTypeAliasDeclaration(declaration)) { + return declaration.tsPlusCompanionTags || []; } - if (parameter.questionToken) { - return grammarErrorOnNode(parameter.questionToken, Diagnostics.An_index_signature_parameter_cannot_have_a_question_mark); + return []; + } + function collectTsPlusMacroTags(declaration: Declaration) { + if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration) || isCallSignatureDeclaration(declaration)) { + return declaration.tsPlusMacroTags || []; } - if (parameter.initializer) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_cannot_have_an_initializer); + return []; + } + function collectTsPlusUnifyTags(declaration: Declaration) { + if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { + return declaration.tsPlusUnifyTags || []; } - if (!parameter.type) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_must_have_a_type_annotation); + return []; + } + function collectTsPlusStaticTags(declaration: Node) { + if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration) || isClassDeclaration(declaration)) { + return declaration.tsPlusStaticTags || []; } - const type = getTypeFromTypeNode(parameter.type); - if (someType(type, t => !!(t.flags & TypeFlags.StringOrNumberLiteralOrUnique)) || isGenericType(type)) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_cannot_be_a_literal_type_or_generic_type_Consider_using_a_mapped_object_type_instead); + return []; + } + function collectTsPlusOperatorTags(declaration: Node) { + if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { + return declaration.tsPlusOperatorTags || []; } - if (!everyType(type, isValidIndexKeyType)) { - return grammarErrorOnNode(parameter.name, Diagnostics.An_index_signature_parameter_type_must_be_string_number_symbol_or_a_template_literal_type); + return []; + } + function collectTsPlusPipeableOperatorTags(declaration: Node) { + if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { + return declaration.tsPlusPipeableOperatorTags || []; } - if (!node.type) { - return grammarErrorOnNode(node, Diagnostics.An_index_signature_must_have_a_type_annotation); + return []; + } + function collectTsPlusGetterTags(declaration: Node) { + if (isVariableDeclaration(declaration) || isFunctionDeclaration(declaration)) { + return declaration.tsPlusGetterTags || []; } - return false; + return []; } - - function checkGrammarIndexSignature(node: IndexSignatureDeclaration) { - // Prevent cascading error by short-circuit - return checkGrammarModifiers(node) || checkGrammarIndexSignatureParameters(node); + function createTsPlusSignature(call: Signature, exportName: string, file: SourceFile): TsPlusSignature { + const signature = cloneSignature(call) as TsPlusSignature + signature.tsPlusTag = "TsPlusSignature"; + signature.tsPlusFile = file; + signature.tsPlusExportName = exportName; + signature.tsPlusOriginal = call; + return signature } - - 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); + function thisifyTsPlusSignature(declaration: FunctionDeclaration | VariableDeclaration, call: Signature, exportName: string, file: SourceFile, reportDiagnostic: (diagnostic: DiagnosticMessage) => void) { + const signature = createTsPlusSignature(call, exportName, file); + if (isSelfARestParameter(signature)) { + reportDiagnostic(Diagnostics.The_first_parameter_of_a_fluent_function_cannot_be_a_rest_parameter); + return undefined; } - return false; + const target = signature.parameters[0]; + signature.thisParameter = createSymbolWithType(createSymbol(target.flags, "this" as __String), getTypeOfSymbol(target)); + const typePredicate = getTypePredicateOfSignature(call); + if (typePredicate && typePredicate.parameterIndex === 0) { + signature.resolvedTypePredicate = createTypePredicate( + typePredicate.kind === TypePredicateKind.Identifier ? TypePredicateKind.This : TypePredicateKind.AssertsThis, + "this", + 0, + typePredicate.type + ); + } + signature.parameters = signature.parameters.slice(1, signature.parameters.length); + signature.minArgumentCount = signature.minArgumentCount - 1; + signature.tsPlusDeclaration = declaration; + signature.resolvedReturnType = call.resolvedReturnType; + return signature; } - - function checkGrammarTypeArguments(node: Node, typeArguments: NodeArray | undefined): boolean { - return checkGrammarForDisallowedTrailingComma(typeArguments) || - checkGrammarForAtLeastOneTypeArgument(node, typeArguments); + function createTsPlusFluentSymbol(name: string, signatures: TsPlusSignature[]): TsPlusFluentSymbol { + const symbol = createSymbol(SymbolFlags.Function, name as __String) as TsPlusFluentSymbol; + symbol.tsPlusTag = TsPlusSymbolTag.Fluent; + symbol.tsPlusResolvedSignatures = signatures; + symbol.tsPlusName = name; + return symbol; } - - 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 createTsPlusFluentSymbolWithType(name: string, allSignatures: TsPlusSignature[]): TransientSymbol { + const symbol = createTsPlusFluentSymbol(name, allSignatures); + const type = createAnonymousType(symbol, emptySymbols, allSignatures, [], []); + return createSymbolWithType(symbol, type); + } + function createTsPlusGetterVariableSymbol(name: string, dataFirst: VariableDeclaration & { name: Identifier }, returnType: Type, selfType: Type) { + const symbol = createSymbol(SymbolFlags.Property, name as __String) as TsPlusGetterVariableSymbol; + getSymbolLinks(symbol).type = returnType; + symbol.tsPlusTag = TsPlusSymbolTag.GetterVariable; + symbol.tsPlusDeclaration = dataFirst; + symbol.tsPlusSelfType = selfType; + symbol.tsPlusName = name; + return symbol; } + function createTsPlusStaticFunctionSymbol(name: string, declaration: FunctionDeclaration | VariableDeclaration, signatures: Signature[]): TsPlusStaticFunctionSymbol { + const symbol = createSymbol(SymbolFlags.Function, name as __String) as TsPlusStaticFunctionSymbol; + symbol.tsPlusTag = TsPlusSymbolTag.StaticFunction; + symbol.tsPlusDeclaration = declaration; + symbol.tsPlusResolvedSignatures = signatures; + symbol.tsPlusName = name; + return symbol; + } + function createTsPlusPipeableDeclarationSymbol(name: string, declaration: FunctionDeclaration | VariableDeclarationWithFunction | VariableDeclarationWithFunctionType) { + const symbol = createSymbol(SymbolFlags.Function, name as __String) as TsPlusPipeableDeclarationSymbol; + symbol.tsPlusTag = TsPlusSymbolTag.PipeableDeclaration; + symbol.tsPlusDeclaration = declaration; + return symbol; + } + function createTsPlusStaticValueSymbol(name: string, declaration: VariableDeclaration | ClassDeclaration, originalSymbol: Symbol): Symbol { + const symbol = cloneSymbol(originalSymbol) as TsPlusStaticValueSymbol; + symbol.tsPlusTag = TsPlusSymbolTag.StaticValue; + symbol.tsPlusResolvedSignatures = []; + symbol.tsPlusName = name; + symbol.tsPlusDeclaration = declaration as (VariableDeclaration | ClassDeclaration) & { name: Identifier }; + symbol.escapedName = name as __String; + let patchedDeclaration: VariableDeclaration | ClassDeclaration + if (isVariableDeclaration(declaration)) { + patchedDeclaration = factory.updateVariableDeclaration( + declaration, + factory.createIdentifier(name), + declaration.exclamationToken, + declaration.type, + declaration.initializer + ); + } + else { + patchedDeclaration = factory.updateClassDeclaration( + declaration, + declaration.modifiers, + factory.createIdentifier(name), + declaration.typeParameters, + declaration.heritageClauses, + declaration.members + ) + } + setParent(patchedDeclaration, declaration.parent); + setParent(patchedDeclaration.name!, patchedDeclaration) + patchedDeclaration.name!.tsPlusName = name; + symbol.declarations = [patchedDeclaration]; + patchedDeclaration.jsDoc = getJSDocCommentsAndTags(declaration) as JSDoc[]; - function checkGrammarHeritageClause(node: HeritageClause): boolean { - const types = node.types; - if (checkGrammarForDisallowedTrailingComma(types)) { - return true; + const originalSymbolLinks = getSymbolLinks(originalSymbol); + const symbolLinks = getSymbolLinks(symbol); + symbolLinks.uniqueESSymbolType = originalSymbolLinks.uniqueESSymbolType; + symbolLinks.declaredType = originalSymbolLinks.declaredType; + + return symbol; + } + function createTsPlusGetterFunctionSymbol(name: string, dataFirst: FunctionDeclaration, returnType: Type, selfType: Type) { + const symbol = createSymbol(SymbolFlags.Property, name as __String) as TsPlusGetterSymbol; + getSymbolLinks(symbol).type = returnType; + symbol.tsPlusTag = TsPlusSymbolTag.Getter; + symbol.tsPlusDeclaration = dataFirst; + symbol.tsPlusSelfType = selfType; + symbol.tsPlusName = name; + return symbol; + } + function getTsPlusFluentSignaturesForFunctionDeclaration(file: SourceFile, exportName: string, dataFirst: FunctionDeclaration) { + function reportDiagnostic(message: DiagnosticMessage) { + error(dataFirst, message); } - if (types && types.length === 0) { - const listType = tokenToString(node.token); - return grammarErrorAtPos(node, types.pos, 0, Diagnostics._0_list_cannot_be_empty, listType); + const type = getTypeOfNode(dataFirst); + const signatures = getSignaturesOfType(type, SignatureKind.Call); + const thisifiedSignatures = signatures.map((signature) => thisifyTsPlusSignature(dataFirst, signature, exportName, file, reportDiagnostic)) + if (!isEachDefined(thisifiedSignatures)) { + return; } - return some(types, checkGrammarExpressionWithTypeArguments); + const thisifiedType = createAnonymousType(undefined, emptySymbols, thisifiedSignatures as TsPlusSignature[], [], []) + return [thisifiedType, thisifiedSignatures] as const; } - - function checkGrammarExpressionWithTypeArguments(node: ExpressionWithTypeArguments | TypeQueryNode) { - if (isExpressionWithTypeArguments(node) && isImportKeyword(node.expression) && node.typeArguments) { - return grammarErrorOnNode(node, Diagnostics.This_use_of_import_is_invalid_import_calls_can_be_written_but_they_must_have_parentheses_and_cannot_have_type_arguments); + function getTsPlusFluentSignaturesForVariableDeclaration(file: SourceFile, exportName: string, declaration: VariableDeclaration & { name: Identifier }) { + function reportDiagnostic(message: DiagnosticMessage) { + error(declaration, message); + } + const type = getTypeOfNode(declaration); + const signatures = getSignaturesOfType(type, SignatureKind.Call); + if(signatures.every((sigSymbol) => sigSymbol.parameters.every((paramSymbol) => paramSymbol.valueDeclaration && isVariableLike(paramSymbol.valueDeclaration) && isParameterDeclaration(paramSymbol.valueDeclaration)))) { + let thisifiedSignatures = signatures.map((signature) => thisifyTsPlusSignature(declaration, signature, exportName, file, reportDiagnostic)) + if (!isEachDefined(thisifiedSignatures)) { + return; + } + const thisifiedType = createAnonymousType(undefined, emptySymbols, thisifiedSignatures as TsPlusSignature[], [], []); + return [thisifiedType, thisifiedSignatures] as const; } - return checkGrammarTypeArguments(node, node.typeArguments); } - - function checkGrammarClassDeclarationHeritageClauses(node: ClassLikeDeclaration) { - let seenExtendsClause = false; - let seenImplementsClause = false; - - if (!checkGrammarModifiers(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 getTsPlusGetterSymbolForFunctionDeclaration(_name: string, _dataFirst: FunctionDeclaration) { + return (selfNode: Expression) => { + const res = checkTsPlusCustomCall( + _dataFirst, + selfNode, + [selfNode], + CheckMode.Normal + ); + if (isErrorType(res)) { + return void 0; + } + return createTsPlusGetterFunctionSymbol(_name, _dataFirst, res, getTypeOfNode(selfNode)); + }; + } + function getTsPlusGetterSymbolForVariableDeclaration(name: string, declaration: VariableDeclaration & { name: Identifier }) { + return (selfNode: Expression) => { + const res = checkTsPlusCustomCall( + declaration, + selfNode, + [selfNode], + CheckMode.Normal + ); + if (isErrorType(res)) { + return void 0; } + return createTsPlusGetterVariableSymbol(name, declaration, res, getTypeOfNode(selfNode)); + }; + } + function getTsPlusStaticSymbolForFunctionDeclaration(file: SourceFile, exportName: string, name: string, dataFirst: FunctionDeclaration | VariableDeclarationWithFunction) { + const signatures = getSignaturesOfType(getTypeOfNode(dataFirst), SignatureKind.Call); + const methods = map(signatures, (s) => createTsPlusSignature(s, exportName, file)); + const symbol = createTsPlusStaticFunctionSymbol(name, dataFirst, methods); + const final = createAnonymousType(symbol, emptySymbols, methods, [], []); + return [final, createSymbolWithType(symbol, final)] as const; + } + function augmentPipeableIdentifierSymbol(identifier: Identifier, target: string, _exportName: string, name: string, dataFirstType: () => Type, declaration: FunctionDeclaration | VariableDeclarationWithFunction) { + const identifierType = getTypeOfNode(identifier); + if (identifierType.symbol) { + (identifierType.symbol as TsPlusPipeableIdentifierSymbol).tsPlusTag = TsPlusSymbolTag.PipeableIdentifier; + (identifierType.symbol as TsPlusPipeableIdentifierSymbol).tsPlusDeclaration = declaration; + (identifierType.symbol as TsPlusPipeableIdentifierSymbol).tsPlusTypeName = target; + (identifierType.symbol as TsPlusPipeableIdentifierSymbol).tsPlusName = name; + (identifierType.symbol as TsPlusPipeableIdentifierSymbol).getTsPlusDataFirstType = dataFirstType; } } + function isPipeableSelfARestParameter(signature: Signature): boolean { + return getSignaturesOfType(getReturnTypeOfSignature(signature), SignatureKind.Call).find(isSelfARestParameter) != null; + } + function getTsPlusFluentSignatureForPipeableFunction(file: SourceFile, exportName: string, name: string, pipeable: FunctionDeclaration, thisify = false): [Type, TsPlusSignature[]] | undefined { + function reportDiagnostic(diagnostic: DiagnosticMessage) { + error(pipeable, diagnostic); + } + const type = getTypeOfNode(pipeable); + const signatures = getSignaturesOfType(type, SignatureKind.Call); + if (pipeable.body) { + const returnStatement = find( + pipeable.body.statements, + (s): s is ReturnStatement & { expression: ArrowFunction } => + isReturnStatement(s) && !!s.expression && isArrowFunction(s.expression) + ); + if (returnStatement && returnStatement.expression.parameters.length === 1) { + if (signatures.find(isPipeableSelfARestParameter)) { + error(pipeable, Diagnostics.The_first_parameter_of_a_pipeable_annotated_function_cannot_be_a_rest_parameter); + return; + } + const tsPlusSignatures = flatMap(signatures, (sig) => { + const returnType = getReturnTypeOfSignature(sig); + const returnSignatures = getSignaturesOfType(returnType, SignatureKind.Call); + return flatMap(returnSignatures, (rsig) => { + let newSig = createTsPlusSignature(sig, exportName, file); + newSig.parameters = [...rsig.parameters, ...sig.parameters]; + newSig.typeParameters = [...(rsig.typeParameters ?? []), ...(sig.typeParameters ?? [])]; + newSig.resolvedReturnType = getReturnTypeOfSignature(rsig); + newSig.minArgumentCount = newSig.minArgumentCount + 1; + const newDecl = factory.updateFunctionDeclaration( + pipeable, + pipeable.modifiers, + pipeable.asteriskToken, + pipeable.name, + [...(returnStatement.expression.typeParameters ?? []), ...(pipeable.typeParameters ?? [])], + [...returnStatement.expression.parameters, ...pipeable.parameters], + returnStatement.expression.type, + undefined + ); - 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); - } + newDecl.jsDoc = pipeable.jsDoc; + newDecl.symbol = createTsPlusPipeableDeclarationSymbol(name, pipeable); + setParent(newDecl, pipeable.parent); + setOriginalNode(newDecl, pipeable) - seenExtendsClause = true; - } - else { - Debug.assert(heritageClause.token === SyntaxKind.ImplementsKeyword); - return grammarErrorOnFirstToken(heritageClause, Diagnostics.Interface_declaration_cannot_have_implements_clause); - } + newSig.declaration = newDecl; + newSig.tsPlusDeclaration = pipeable; - // Grammar checking heritageClause inside class declaration - checkGrammarHeritageClause(heritageClause); + if (thisify) { + const thisified = thisifyTsPlusSignature(pipeable, newSig, exportName, file, reportDiagnostic); + if (!thisified) { + return; + } + newSig = thisified; + } + newSig.tsPlusPipeable = true; + return newSig; + }); + }) as TsPlusSignature[]; + const dataFirstType = createAnonymousType(undefined, emptySymbols, tsPlusSignatures, [], []); + return [dataFirstType, tsPlusSignatures]; } } - 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 filteredSignatures = filter(signatures, (sig) => { + if (!sig.declaration || !sig.declaration.type) { + return false + } + if (isFunctionTypeNode(sig.declaration.type) && sig.declaration.type.parameters.length === 1) { + return true + } + if (isCallSignatureDeclaration(sig.declaration) && sig.declaration.parameters.length === 1) { + return true + } + return false + }) + if (filteredSignatures.find(isPipeableSelfARestParameter)) { + error(pipeable, Diagnostics.The_first_parameter_of_a_pipeable_annotated_function_cannot_be_a_rest_parameter); + return; } + if (filteredSignatures.length > 0) { + const tsPlusSignatures = flatMap(filteredSignatures, (sig) => { + const returnType = getReturnTypeOfSignature(sig); + const returnSignatures = getSignaturesOfType(returnType, SignatureKind.Call); + return flatMap(returnSignatures, (rsig) => { + let newSig = createTsPlusSignature(sig, exportName, file); + newSig.parameters = [...rsig.parameters, ...sig.parameters]; + newSig.typeParameters = [...(rsig.typeParameters ?? []), ...(sig.typeParameters ?? [])]; + newSig.resolvedReturnType = getReturnTypeOfSignature(rsig); + newSig.minArgumentCount = newSig.minArgumentCount + 1; + const newDecl = factory.updateFunctionDeclaration( + pipeable, + pipeable.modifiers, + pipeable.asteriskToken, + pipeable.name, + [...(rsig.declaration?.typeParameters as NodeArray ?? []), ...(sig.declaration?.typeParameters as NodeArray ?? [])], + [...(rsig.declaration?.parameters as NodeArray ?? []), ...(sig.declaration?.parameters as NodeArray ?? [])], + rsig.declaration?.type as TypeNode, + undefined + ); - const computedPropertyName = node as ComputedPropertyName; - if (computedPropertyName.expression.kind === SyntaxKind.BinaryExpression && (computedPropertyName.expression as BinaryExpression).operatorToken.kind === SyntaxKind.CommaToken) { - return grammarErrorOnNode(computedPropertyName.expression, Diagnostics.A_comma_expression_is_not_allowed_in_a_computed_property_name); - } - return false; - } + newDecl.jsDoc = pipeable.jsDoc; + newDecl.symbol = createTsPlusPipeableDeclarationSymbol(name, pipeable); + setParent(newDecl, pipeable.parent); + setOriginalNode(newDecl, pipeable); - 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); - } + newSig.declaration = newDecl; + newSig.tsPlusDeclaration = pipeable; + + if (thisify) { + const thisifiedSignature = thisifyTsPlusSignature(pipeable, newSig, exportName, file, reportDiagnostic); + if (!thisifiedSignature) { + return; + } + newSig = thisifiedSignature; + } + newSig.tsPlusPipeable = true; + return newSig; + }); + }) as TsPlusSignature[]; + const dataFirstType = createAnonymousType(undefined, emptySymbols, tsPlusSignatures, [], []); + return [dataFirstType, tsPlusSignatures]; } + return undefined; } - - function checkGrammarForInvalidQuestionMark(questionToken: QuestionToken | undefined, message: DiagnosticMessage): boolean { - return !!questionToken && grammarErrorOnNode(questionToken, message); + function isVariableDeclarationWithFunction(node: VariableDeclaration): node is VariableDeclarationWithFunction { + return isIdentifier(node.name) && !!node.initializer && (isArrowFunction(node.initializer) || isFunctionExpression(node.initializer)); } - - function checkGrammarForInvalidExclamationToken(exclamationToken: ExclamationToken | undefined, message: DiagnosticMessage): boolean { - return !!exclamationToken && grammarErrorOnNode(exclamationToken, message); + function isEachDefined(array: (A | undefined)[]): array is A[] { + for (const a of array) { + if (a === undefined) { + return false; + } + } + return true; } - - 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); - } + function getTsPlusFluentSignatureForPipeableVariableDeclaration(file: SourceFile, exportName: string, name: string, pipeable: VariableDeclarationWithFunction | VariableDeclarationWithFunctionType, thisify = false): [Type, TsPlusSignature[]] | undefined { + function reportDiagnostic(diagnostic: DiagnosticMessage) { + error(pipeable, diagnostic); + } + const type = getTypeOfNode(pipeable); + const signatures = getSignaturesOfType(type, SignatureKind.Call); + if (isVariableDeclarationWithFunction(pipeable)) { + const initializer = pipeable.initializer; + const body = initializer.body; + let returnFn: ArrowFunction | FunctionTypeNode | undefined; + if (isBlock(body)) { + const candidate = find( + body.statements, + (s): s is ReturnStatement & { expression: ArrowFunction } => + isReturnStatement(s) && !!s.expression && isArrowFunction(s.expression) + ); + if (candidate) { + returnFn = candidate.expression; } - 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); + else if (isArrowFunction(body)) { + returnFn = body; } + if (returnFn && returnFn.parameters.length === 1) { + if (signatures.find(isPipeableSelfARestParameter)) { + error(pipeable, Diagnostics.The_first_parameter_of_a_pipeable_annotated_function_cannot_be_a_rest_parameter); + return; + } + const tsPlusSignatures = flatMap(signatures, (sig) => { + const returnType = getReturnTypeOfSignature(sig); + const returnSignatures = getSignaturesOfType(returnType, SignatureKind.Call); + return flatMap(returnSignatures, (rsig) => { + let newSig = createTsPlusSignature(sig, exportName, file); + newSig.parameters = [...rsig.parameters, ...sig.parameters]; + newSig.typeParameters = [...(rsig.typeParameters ?? []), ...(sig.typeParameters ?? [])]; + newSig.resolvedReturnType = getReturnTypeOfSignature(rsig); + newSig.minArgumentCount = newSig.minArgumentCount + 1; + let newDecl: ArrowFunction | FunctionExpression | FunctionTypeNode; + if (isArrowFunction(initializer)) { + newDecl = factory.updateArrowFunction( + initializer, + initializer.modifiers, + [...(returnFn!.typeParameters ?? []), ...(initializer.typeParameters ?? [])], + [...returnFn!.parameters, ...initializer.parameters], + returnFn!.type, + initializer.equalsGreaterThanToken, + factory.createBlock([]) + ); + } + else { + newDecl = factory.updateFunctionExpression( + initializer, + initializer.modifiers, + initializer.asteriskToken, + initializer.name, + [...(returnFn!.typeParameters ?? []), ...(initializer.typeParameters ?? [])], + [...returnFn!.parameters, ...initializer.parameters], + returnFn!.type, + factory.createBlock([]) + ); + } - if (prop.kind === SyntaxKind.ShorthandPropertyAssignment && !inDestructuring && prop.objectAssignmentInitializer) { - // having objectAssignmentInitializer is only valid in ObjectAssignmentPattern - // outside of destructuring it is a syntax error - 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); - } + newDecl.jsDoc = pipeable.jsDoc; + newDecl.symbol = createTsPlusPipeableDeclarationSymbol(name, pipeable); + setParent(newDecl, pipeable.parent); + setOriginalNode(newDecl, pipeable); - if (name.kind === SyntaxKind.PrivateIdentifier) { - grammarErrorOnNode(name, Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); - } + newSig.declaration = newDecl; + newSig.tsPlusDeclaration = pipeable; - // Modifiers are never allowed on properties except for 'async' on a method declaration - if (canHaveModifiers(prop) && prop.modifiers) { - for (const mod of prop.modifiers) { - if (isModifier(mod) && (mod.kind !== SyntaxKind.AsyncKeyword || prop.kind !== SyntaxKind.MethodDeclaration)) { - grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); - } - } + if (thisify) { + const thisifiedSignature = thisifyTsPlusSignature(pipeable, newSig, exportName, file, reportDiagnostic); + if (!thisifiedSignature) { + return; + } + newSig = thisifiedSignature; + } + newSig.tsPlusPipeable = true; + return newSig; + }); + }) as TsPlusSignature[]; + const dataFirstType = createAnonymousType(type.symbol, emptySymbols, tsPlusSignatures, [], []); + return [dataFirstType, tsPlusSignatures]; } - else if (canHaveIllegalModifiers(prop) && prop.modifiers) { - for (const mod of prop.modifiers) { - if (isModifier(mod)) { - grammarErrorOnNode(mod, Diagnostics._0_modifier_cannot_be_used_here, getTextOfNode(mod)); - } - } + } + const filteredSignatures = filter(signatures, (sig) => { + if (!sig.declaration || !sig.declaration.type) { + return false } - - // 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: - case SyntaxKind.PropertyAssignment: - // Grammar checking for computedPropertyName and shorthandPropertyAssignment - checkGrammarForInvalidExclamationToken(prop.exclamationToken, Diagnostics.A_definite_assignment_assertion_is_not_permitted_in_this_context); - 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: - Debug.assertNever(prop, "Unexpected syntax kind:" + (prop as Node).kind); + if (isFunctionTypeNode(sig.declaration.type) && sig.declaration.type.parameters.length === 1) { + return true + } + if (isCallSignatureDeclaration(sig.declaration) && sig.declaration.parameters.length === 1) { + return true } + return false + }) + if (filteredSignatures.find(isPipeableSelfARestParameter)) { + error(pipeable, Diagnostics.The_first_parameter_of_a_pipeable_annotated_function_cannot_be_a_rest_parameter); + return; + } + if (filteredSignatures.length > 0) { + const tsPlusSignatures = flatMap(filteredSignatures, (sig) => { + const returnFn = sig.declaration!.type! as FunctionTypeNode + const returnType = getReturnTypeOfSignature(sig); + const returnSignatures = getSignaturesOfType(returnType, SignatureKind.Call); + return flatMap(returnSignatures, (rsig) => { + let newSig = createTsPlusSignature(sig, exportName, file); + newSig.parameters = [...rsig.parameters, ...sig.parameters]; + newSig.typeParameters = [...(rsig.typeParameters ?? []), ...(sig.typeParameters ?? [])]; + newSig.resolvedReturnType = getReturnTypeOfSignature(rsig); + newSig.minArgumentCount = newSig.minArgumentCount + 1; + const newDecl = factory.createFunctionTypeNode( + factory.createNodeArray([...(returnFn.typeParameters ?? []), ...(sig.declaration?.typeParameters as NodeArray ?? [])]), + factory.createNodeArray([...returnFn.parameters, ...(sig.declaration?.parameters as NodeArray ?? [])]), + returnFn.type + ); - if (!inDestructuring) { - const effectiveName = getEffectivePropertyNameForPropertyNameNode(name); - if (effectiveName === undefined) { - continue; - } + newDecl.jsDoc = pipeable.parent.parent.jsDoc; + newDecl.symbol = createTsPlusPipeableDeclarationSymbol(name, pipeable); + setParent(newDecl, pipeable.parent); + setOriginalNode(newDecl, pipeable); - const existingKind = seen.get(effectiveName); - if (!existingKind) { - seen.set(effectiveName, currentKind); - } - else { - if ((currentKind & DeclarationMeaning.Method) && (existingKind & DeclarationMeaning.Method)) { - grammarErrorOnNode(name, Diagnostics.Duplicate_identifier_0, getTextOfNode(name)); - } - else if ((currentKind & DeclarationMeaning.PropertyAssignment) && (existingKind & DeclarationMeaning.PropertyAssignment)) { - grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_multiple_properties_with_the_same_name, 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); + newSig.declaration = newDecl; + newSig.tsPlusDeclaration = pipeable; + + if (thisify) { + const thisifiedSignature = thisifyTsPlusSignature(pipeable, newSig, exportName, file, reportDiagnostic); + if (!thisifiedSignature) { + return; } + newSig = thisifiedSignature; } - else { - return grammarErrorOnNode(name, Diagnostics.An_object_literal_cannot_have_property_and_accessor_with_the_same_name); - } - } - } + newSig.tsPlusPipeable = true; + return newSig; + }); + }) as TsPlusSignature[]; + const dataFirstType = createAnonymousType(type.symbol, emptySymbols, tsPlusSignatures, [], []); + return [dataFirstType, tsPlusSignatures]; } + return undefined; } - - function checkGrammarJsxElement(node: JsxOpeningLikeElement) { - checkGrammarJsxName(node.tagName); - 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; - const escapedText = getEscapedTextOfJsxAttributeName(name); - if (!seen.get(escapedText)) { - seen.set(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 isSelfARestParameter(signature: Signature): boolean { + if ( + signature.parameters[0] && + signature.parameters[0].declarations && + signature.parameters[0].declarations.find((decl) => isVariableLike(decl) && isParameterDeclaration(decl) && isRestParameter(decl as ParameterDeclaration)) + ) { + return true + } + return false; + } + function addToTypeSymbolCache(symbol: Symbol, tag: string, priority: "before" | "after") { + if (!typeSymbolCache.has(symbol)) { + typeSymbolCache.set(symbol, []); + } + const tags = typeSymbolCache.get(symbol)! + if (!tags.includes(tag)) { + if (priority === "before") { + typeSymbolCache.set(symbol, [tag, ...tags]) + } else { + tags.push(tag) } } } - - function checkGrammarJsxName(node: JsxTagNameExpression) { - if (isPropertyAccessExpression(node) && isJsxNamespacedName(node.expression)) { - return grammarErrorOnNode(node.expression, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names); + function addToCompanionSymbolCache(symbol: Symbol, tag: string) { + if (!companionSymbolCache.has(symbol)) { + companionSymbolCache.set(symbol, []) } - if (isJsxNamespacedName(node) && getJSXTransformEnabled(compilerOptions) && !isIntrinsicJsxName(node.namespace.escapedText)) { - return grammarErrorOnNode(node, Diagnostics.React_components_cannot_include_JSX_namespace_names); + const tags = companionSymbolCache.get(symbol)! + if (!tags.includes(tag)) { + tags.push(tag) } } - - 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 tryCacheTsPlusGlobalSymbol(declaration: ImportDeclaration): void { + if (declaration.isTsPlusGlobal) { + (declaration.importClause!.namedBindings as NamedImports).elements.forEach((importSpecifier) => { + tsPlusGlobalImportCache.set(importSpecifier.name.escapedText as string, { declaration, importSpecifier, moduleSpecifier: declaration.moduleSpecifier as StringLiteral }); + }) } } - - function checkGrammarForInOrForOfStatement(forInOrOfStatement: ForInOrOfStatement): boolean { - if (checkGrammarStatementInAmbientContext(forInOrOfStatement)) { - return true; + function cacheTsPlusType(declaration: InterfaceDeclaration | TypeAliasDeclaration | ClassDeclaration): void { + unresolvedTypeDeclarations.add(declaration); + } + function tryCacheTsPlusInheritance(typeSymbol: Symbol, heritage: readonly HeritageClause[]): void { + if (!inheritanceSymbolCache.has(typeSymbol)) { + inheritanceSymbolCache.set(typeSymbol, new Set()); } - if (forInOrOfStatement.kind === SyntaxKind.ForOfStatement && forInOrOfStatement.awaitModifier) { - if (!(forInOrOfStatement.flags & NodeFlags.AwaitContext)) { - const sourceFile = getSourceFileOfNode(forInOrOfStatement); - if (isInTopLevelContext(forInOrOfStatement)) { - if (!hasParseDiagnostics(sourceFile)) { - if (!isEffectiveExternalModule(sourceFile, compilerOptions)) { - diagnostics.add(createDiagnosticForNode(forInOrOfStatement.awaitModifier, - Diagnostics.for_await_loops_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)); + const heritageExtensions = inheritanceSymbolCache.get(typeSymbol)!; + forEach(heritage, (clause) => { + forEach(clause.types, (node) => { + if (isIdentifier(node.expression)) { + const nodeType = getTypeOfNode(node.expression); + if (nodeType.flags & TypeFlags.Intersection) { + forEach((nodeType as IntersectionType).types, (type) => { + if (type.symbol) { + heritageExtensions.add(type.symbol); + } + if (type.aliasSymbol) { + heritageExtensions.add(type.aliasSymbol); + } + }) + } + if (nodeType.symbol) { + heritageExtensions.add(nodeType.symbol); + } + if (nodeType.aliasSymbol) { + heritageExtensions.add(nodeType.aliasSymbol) + } + } else if (isCallExpression(node.expression)) { + const resolvedSignature = getResolvedSignature(node.expression); + const returnType = getReturnTypeOfSignature(resolvedSignature); + if (returnType) { + if (returnType.symbol) { + heritageExtensions.add(returnType.symbol); } - switch (moduleKind) { - case ModuleKind.Node16: - case ModuleKind.NodeNext: - if (sourceFile.impliedNodeFormat === ModuleKind.CommonJS) { - diagnostics.add( - createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.The_current_file_is_a_CommonJS_module_and_cannot_use_await_at_the_top_level) - ); - break; + if (returnType.flags & TypeFlags.Intersection) { + forEach((returnType as IntersectionType).types, (type) => { + if (type.symbol) { + heritageExtensions.add(type.symbol) } - // fallthrough - case ModuleKind.ES2022: - case ModuleKind.ESNext: - case ModuleKind.System: - if (languageVersion >= ScriptTarget.ES2017) { - break; + if (type.aliasSymbol) { + heritageExtensions.add(type.aliasSymbol) } - // fallthrough - default: - diagnostics.add( - createDiagnosticForNode(forInOrOfStatement.awaitModifier, - Diagnostics.Top_level_for_await_loops_are_only_allowed_when_the_module_option_is_set_to_es2022_esnext_system_node16_or_nodenext_and_the_target_option_is_set_to_es2017_or_higher - ) - ); - break; - } - } - } - else { - // use of 'for-await-of' in non-async function - if (!hasParseDiagnostics(sourceFile)) { - const diagnostic = createDiagnosticForNode(forInOrOfStatement.awaitModifier, Diagnostics.for_await_loops_are_only_allowed_within_async_functions_and_at_the_top_levels_of_modules); - 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 (isForOfStatement(forInOrOfStatement) && !(forInOrOfStatement.flags & NodeFlags.AwaitContext) && - isIdentifier(forInOrOfStatement.initializer) && forInOrOfStatement.initializer.escapedText === "async") { - grammarErrorOnNode(forInOrOfStatement.initializer, Diagnostics.The_left_hand_side_of_a_for_of_statement_may_not_be_async); - return false; - } - - if (forInOrOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList) { - const variableList = forInOrOfStatement.initializer as VariableDeclarationList; - 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); + const type = getTypeOfNode(node); + if (type.symbol) { + heritageExtensions.add(type.symbol) } - } - } - - return false; + }) + }) } - - function checkGrammarAccessor(accessor: AccessorDeclaration): boolean { - if (!(accessor.flags & NodeFlags.Ambient) && (accessor.parent.kind !== SyntaxKind.TypeLiteral) && (accessor.parent.kind !== SyntaxKind.InterfaceDeclaration)) { - if (languageVersion < ScriptTarget.ES5) { - return grammarErrorOnNode(accessor.name, Diagnostics.Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher); - } - if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(accessor.name)) { - return grammarErrorOnNode(accessor.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); - } - if (accessor.body === undefined && !hasSyntacticModifier(accessor, ModifierFlags.Abstract)) { - return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{"); - } - } - if (accessor.body) { - if (hasSyntacticModifier(accessor, ModifierFlags.Abstract)) { - return grammarErrorOnNode(accessor, Diagnostics.An_abstract_accessor_cannot_have_an_implementation); - } - if (accessor.parent.kind === SyntaxKind.TypeLiteral || accessor.parent.kind === SyntaxKind.InterfaceDeclaration) { - return grammarErrorOnNode(accessor.body, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts); - } - } - 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); + function tryCacheUnionInheritance(members: Type[], parent: Type): void { + const parentSymbol = parent.symbol ?? parent.aliasSymbol; + if (parentSymbol) { + for (const member of members) { + const memberSymbol = member.symbol ?? member.aliasSymbol; + if (memberSymbol) { + if (!inheritanceSymbolCache.has(memberSymbol)) { + inheritanceSymbolCache.set(memberSymbol, new Set()); + } + inheritanceSymbolCache.get(memberSymbol)!.add(parentSymbol); + if (memberSymbol.declarations) { + for (const declaration of memberSymbol.declarations) { + if ((isInterfaceDeclaration(declaration) || isClassDeclaration(declaration)) && declaration.heritageClauses) { + tryCacheTsPlusInheritance(memberSymbol, declaration.heritageClauses); + } + } + } + } } } - 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 getTsPlusSourceFileCache(tag: string) { + return tsPlusFiles.has(tag) ? tsPlusFiles.get(tag)! : (tsPlusFiles.set(tag, new Set()), tsPlusFiles.get(tag)!); } - - function getAccessorThisParameter(accessor: AccessorDeclaration): ParameterDeclaration | undefined { - if (accessor.parameters.length === (accessor.kind === SyntaxKind.GetAccessor ? 1 : 2)) { - return getThisParameter(accessor); + function cacheTsPlusCompanion(declaration: ClassDeclaration | InterfaceDeclaration | TypeAliasDeclaration): void { + unresolvedCompanionDeclarations.add(declaration) + } + function cacheTsPlusStaticVariable(file: SourceFile, declaration: VariableDeclarationWithIdentifier | ClassDeclarationWithIdentifier) { + const staticTags = collectTsPlusStaticTags(declaration); + if (staticTags.length > 0) { + const symbol = getSymbolAtLocation(declaration.name); + if (symbol) { + for (const { target, name } of staticTags) { + getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); + if (!unresolvedStaticCache.get(target)) { + unresolvedStaticCache.set(target, new Map()); + } + const map = unresolvedStaticCache.get(target)! + map.set(name, { + target, + name, + symbol, + declaration, + exportName: symbol.escapedName.toString(), + definition: file + }) + } + } } } - - 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)); + function cacheTsPlusFluentVariable(file: SourceFile, declaration: VariableDeclarationWithIdentifier) { + const fluentTags = collectTsPlusFluentTags(declaration); + for (const tag of fluentTags) { + getTsPlusSourceFileCache(tag.target).add(getSourceFileOfNode(declaration)); + if (!unresolvedFluentCache.has(tag.target)) { + unresolvedFluentCache.set(tag.target, new Map()); } - let parent = walkUpParenthesizedTypes(node.parent); - if (isInJSFile(parent) && isJSDocTypeExpression(parent)) { - const host = getJSDocHost(parent); - if (host) { - parent = getSingleVariableOfVariableStatement(host) || host; - } + const map = unresolvedFluentCache.get(tag.target)!; + if (!map.has(tag.name)) { + map.set(tag.name, { + target: tag.target, + name: tag.name, + definition: new Set([{ + definition: file, + declaration: declaration as VariableDeclaration & { name: Identifier }, + exportName: declaration.name.escapedText.toString(), + priority: tag.priority, + }]) + }); } - 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 as VariableDeclaration).name, Diagnostics.A_variable_whose_type_is_a_unique_symbol_type_must_be_const); - } - break; - - case SyntaxKind.PropertyDeclaration: - if (!isStatic(parent) || - !hasEffectiveReadonlyModifier(parent)) { - return grammarErrorOnNode((parent as PropertyDeclaration).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 as PropertySignature).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 { + const extension = map.get(tag.name)!; + extension.definition.add({ + definition: file, + declaration: declaration as VariableDeclaration & { name: Identifier }, + exportName: declaration.name.escapedText.toString(), + priority: tag.priority, + }); } - } - 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 cacheTsPlusGetterVariable(file: SourceFile, declaration: VariableDeclarationWithIdentifier) { + for (const { target, name } of collectTsPlusGetterTags(declaration)) { + getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); + if (!getterCache.has(target)) { + getterCache.set(target, new Map()); } + const map = getterCache.get(target)!; + map.set(name, { + patched: getTsPlusGetterSymbolForVariableDeclaration( + name, + (declaration as VariableDeclaration & { name: Identifier }) + ), + exportName: declaration.name.escapedText.toString(), + definition: file, + declaration, + }); } } - - function checkGrammarForInvalidDynamicName(node: DeclarationName, message: DiagnosticMessage) { - if (isNonBindableDynamicName(node)) { - return grammarErrorOnNode(node, message); + function cacheTsPlusOperatorFunction(file: SourceFile, declaration: FunctionDeclaration) { + if(declaration.name && isIdentifier(declaration.name)) { + const operatorTags = collectTsPlusOperatorTags(declaration); + if (operatorTags.length > 0) { + const symbol = getSymbolAtLocation(declaration.name); + if (symbol) { + for (const tag of operatorTags) { + getTsPlusSourceFileCache(tag.target).add(getSourceFileOfNode(declaration)); + if (!operatorCache.has(tag.target)) { + operatorCache.set(tag.target, new Map()); + } + const map = operatorCache.get(tag.target)!; + if (!map.has(tag.name)) { + map.set(tag.name, []); + } + map.get(tag.name)!.push({ + patched: symbol, + exportName: declaration.name.escapedText.toString(), + definition: file, + priority: tag.priority, + }); + } + } + } } } - - 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, "{"); + function cacheTsPlusOperatorVariable(file: SourceFile, declaration: VariableDeclarationWithIdentifier) { + const operatorTags = collectTsPlusOperatorTags(declaration); + if (operatorTags.length > 0) { + const symbol = getSymbolAtLocation(declaration.name); + if (symbol) { + for (const tag of operatorTags) { + getTsPlusSourceFileCache(tag.target).add(getSourceFileOfNode(declaration)); + if (!operatorCache.has(tag.target)) { + operatorCache.set(tag.target, new Map()); + } + const map = operatorCache.get(tag.target)!; + if (!map.has(tag.name)) { + map.set(tag.name, []); + } + map.get(tag.name)!.push({ + patched: symbol, + exportName: declaration.name.escapedText.toString(), + definition: file, + priority: tag.priority, + }); } } - if (checkGrammarForGenerator(node)) { - return true; - } } - - if (isClassLike(node.parent)) { - if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(node.name)) { - return grammarErrorOnNode(node.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); + } + function cacheTsPlusFluentFunction(file: SourceFile, declaration: FunctionDeclaration) { + const fluentTags = collectTsPlusFluentTags(declaration); + for (const tag of fluentTags) { + getTsPlusSourceFileCache(tag.target).add(getSourceFileOfNode(declaration)); + if (!unresolvedFluentCache.has(tag.target)) { + unresolvedFluentCache.set(tag.target, new Map()); } - // 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); + const map = unresolvedFluentCache.get(tag.target)!; + if (!map.has(tag.name)) { + map.set(tag.name, { + target: tag.target, + name: tag.name, + definition: new Set([{ + definition: file, + declaration, + exportName: declaration.name!.escapedText.toString(), + priority: tag.priority, + }]), + }); } - 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 { + const extension = map.get(tag.name)!; + extension.definition.add({ + definition: file, + declaration, + exportName: declaration.name!.escapedText.toString(), + priority: tag.priority, + }); } } - 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 (isFunctionLikeOrClassStaticBlockDeclaration(current)) { - return grammarErrorOnNode(node, Diagnostics.Jump_target_cannot_cross_function_boundary); - } - - switch (current.kind) { - case SyntaxKind.LabeledStatement: - if (node.label && (current as LabeledStatement).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 as LabeledStatement).statement, /*lookInLabeledStatements*/ true); - - if (isMisplacedContinueLabel) { - return grammarErrorOnNode(node, Diagnostics.A_continue_statement_can_only_jump_to_a_label_of_an_enclosing_iteration_statement); - } - - return false; + function cacheTsPlusPipeableOperatorFunction(file: SourceFile, declaration: FunctionDeclaration) { + if (declaration.name && isIdentifier(declaration.name)) { + const pipeableOperatorTags = collectTsPlusPipeableOperatorTags(declaration); + if (pipeableOperatorTags.length > 0) { + for (const { target, name, priority } of pipeableOperatorTags) { + getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); + if (!unresolvedPipeableOperatorCache.has(target)) { + unresolvedPipeableOperatorCache.set(target, new Map()) } - break; - case SyntaxKind.SwitchStatement: - if (node.kind === SyntaxKind.BreakStatement && !node.label) { - // unlabeled break within switch statement - ok - return false; + const exportName = declaration.name.escapedText.toString(); + let cached: [Type, TsPlusSignature[]] | false = false + const getTypeAndSignatures = (): [Type, TsPlusSignature[]] => { + if (cached === false) { + const resolved = getTsPlusFluentSignatureForPipeableFunction(file, exportName, name, declaration); + if (resolved) { + cached = resolved; + } + else { + error(declaration, Diagnostics.Invalid_declaration_annotated_as_pipeable); + cached = [errorType, []]; + } + } + return [cached[0], [...cached[1]]]; } - break; - default: - if (isIterationStatement(current, /*lookInLabeledStatements*/ false) && !node.label) { - // unlabeled break or continue within iteration statement - ok - return false; + const map = unresolvedPipeableOperatorCache.get(target)!; + if (!map.has(name)) { + map.set(name, { + target, + name, + definition: new Set() + }) } - break; + map.get(name)!.definition.add({ + definition: file, + declaration, + exportName, + priority, + getTypeAndSignatures + }) + augmentPipeableIdentifierSymbol( + declaration.name, + target, + declaration.name.escapedText.toString(), + name, + () => getTypeAndSignatures()[0], + declaration + ); + } } - - 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); + function cacheTsPlusPipeableOperatorVariable(file: SourceFile, declaration: VariableDeclarationWithIdentifier) { + if (declaration.initializer && isFunctionLikeDeclaration(declaration.initializer) || + declaration.type && (isFunctionTypeNode(declaration.type) || isTypeLiteralNode(declaration.type))) { + const pipeableOperatorTags = collectTsPlusPipeableOperatorTags(declaration); + if (pipeableOperatorTags.length > 0) { + for (const { target, name, priority } of pipeableOperatorTags) { + getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); + if (!unresolvedPipeableOperatorCache.has(target)) { + unresolvedPipeableOperatorCache.set(target, new Map()) + } + const exportName = declaration.name.escapedText.toString(); + let cached: [Type, TsPlusSignature[]] | false = false + const getTypeAndSignatures = (): [Type, TsPlusSignature[]] => { + if (cached === false) { + const resolved = getTsPlusFluentSignatureForPipeableVariableDeclaration(file, exportName, name, declaration as VariableDeclarationWithFunction | VariableDeclarationWithFunctionType); + if (resolved) { + cached = resolved; + } + else { + error(declaration, Diagnostics.Invalid_declaration_annotated_as_pipeable); + cached = [errorType, []]; + } + } + return [cached[0], [...cached[1]]]; + } + const map = unresolvedPipeableOperatorCache.get(target)!; + if (!map.has(name)) { + map.set(name, { + target, + name, + definition: new Set() + }) + } + map.get(name)!.definition.add({ + definition: file, + declaration, + exportName, + priority, + getTypeAndSignatures + }) + augmentPipeableIdentifierSymbol( + declaration.name, + target, + declaration.name.escapedText.toString(), + name, + () => getTypeAndSignatures()[0], + declaration as VariableDeclarationWithFunction + ); + } } } - - 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 as PrefixUnaryExpression).operator === SyntaxKind.MinusToken && - (expr as PrefixUnaryExpression).operand.kind === SyntaxKind.NumericLiteral; } - - function isBigIntLiteralExpression(expr: Expression) { - return expr.kind === SyntaxKind.BigIntLiteral || - expr.kind === SyntaxKind.PrefixUnaryExpression && (expr as PrefixUnaryExpression).operator === SyntaxKind.MinusToken && - (expr as PrefixUnaryExpression).operand.kind === SyntaxKind.BigIntLiteral; - } - - function isSimpleLiteralEnumReference(expr: Expression) { - if ((isPropertyAccessExpression(expr) || (isElementAccessExpression(expr) && isStringOrNumberLiteralExpression(expr.argumentExpression))) && - isEntityNameExpression(expr.expression)) { - return !!(checkExpressionCached(expr).flags & TypeFlags.EnumLike); + function cacheTsPlusPipeableFunction(file: SourceFile, declaration: FunctionDeclaration) { + if (declaration.name && isIdentifier(declaration.name)) { + const pipeableTags = collectTsPlusPipeableTags(declaration); + for (const { target, name, priority } of pipeableTags) { + getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); + if (!unresolvedPipeableCache.has(target)) { + unresolvedPipeableCache.set(target, new Map()); + } + const exportName = declaration.name.escapedText.toString() + let cached : [Type, TsPlusSignature[]] | false = false + const getTypeAndSignatures = (): [Type, TsPlusSignature[]] => { + if (cached === false) { + const resolved = getTsPlusFluentSignatureForPipeableFunction(file, exportName, name, declaration, /** thisify */ true); + if (resolved) { + cached = resolved; + } + else { + error(declaration, Diagnostics.Invalid_declaration_annotated_as_pipeable); + cached = [errorType, []]; + } + } + return [cached[0], [...cached[1]]]; + } + const map = unresolvedPipeableCache.get(target)!; + if (!map.has(name)) { + map.set(name, { target, name, definition: new Set() }) + } + const extension = map.get(name)! + extension.definition.add({ + definition: file, + declaration, + exportName, + priority, + getTypeAndSignatures + }) + augmentPipeableIdentifierSymbol( + declaration.name, + target, + declaration.name.escapedText.toString(), + name, + () => getTypeAndSignatures()[0], + declaration + ); + getNodeLinks(declaration).tsPlusPipeableExtension = { + declaration, + definition: file, + exportName, + typeName: target, + funcName: name, + getTypeAndSignatures + } + } } } - - function checkAmbientInitializer(node: VariableDeclaration | PropertyDeclaration | PropertySignature) { - const initializer = node.initializer; - 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); + function cacheTsPlusPipeableVariable(file: SourceFile, declaration: VariableDeclarationWithIdentifier) { + if( + (declaration.initializer && isFunctionLikeDeclaration(declaration.initializer)) || + (declaration.type && isFunctionTypeNode(declaration.type)) || + ((declaration.type) && isTypeLiteralNode(declaration.type) && every(declaration.type.members, isCallSignatureDeclaration)) + ) { + for (const { target, name, priority } of collectTsPlusPipeableTags(declaration)) { + getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); + if (!unresolvedPipeableCache.has(target)) { + unresolvedPipeableCache.set(target, new Map()); + } + const map = unresolvedPipeableCache.get(target)!; + const exportName = declaration.name.escapedText.toString(); + let cached : [Type, TsPlusSignature[]] | false = false + const getTypeAndSignatures = () => { + if (cached === false) { + const resolved = getTsPlusFluentSignatureForPipeableVariableDeclaration( + file, + exportName, + name, + declaration as VariableDeclarationWithFunction | VariableDeclarationWithFunctionType, + /** thisify */ true + ) + if (resolved) { + cached = resolved; + } + else { + error(declaration, Diagnostics.Invalid_declaration_annotated_as_pipeable); + cached = [errorType, []]; + } + } + return cached; } - } - else { - 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 (!map.has(name)) { + map.set(name, { target, name, definition: new Set() }) } - if (isVarConst(node)) { - return grammarErrorOnNode(node, Diagnostics.const_declarations_must_be_initialized); + const extension = map.get(name)! + extension.definition.add({ + definition: file, + declaration, + exportName, + priority, + getTypeAndSignatures + }) + augmentPipeableIdentifierSymbol( + declaration.name, + target, + declaration.name.escapedText.toString(), + name, + () => getTypeAndSignatures()[0], + declaration as VariableDeclarationWithFunction + ); + getNodeLinks(declaration).tsPlusPipeableExtension = { + declaration: declaration as VariableDeclarationWithFunction, + definition: file, + exportName, + typeName: target, + funcName: name, + getTypeAndSignatures } } } - - 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 - : !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); - } - - if ((moduleKind < ModuleKind.ES2015 || getSourceFileOfNode(node).impliedNodeFormat === ModuleKind.CommonJS) && 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); + function cacheTsPlusGetterFunction(file: SourceFile, declaration: FunctionDeclaration) { + if(declaration.name && isIdentifier(declaration.name)) { + for (const { target, name } of collectTsPlusGetterTags(declaration)) { + getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); + if (!getterCache.has(target)) { + getterCache.set(target, new Map()); } + const map = getterCache.get(target)!; + map.set(name, { + patched: getTsPlusGetterSymbolForFunctionDeclaration(name, declaration), + exportName: declaration.name.escapedText.toString(), + definition: file, + declaration + }); } } - return false; } - - function checkGrammarNameInLetOrConstDeclarations(name: Identifier | BindingPattern): boolean { - if (name.kind === SyntaxKind.Identifier) { - if (name.escapedText === "let") { - 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); + function cacheTsPlusStaticFunction(file: SourceFile, declaration: FunctionDeclaration) { + if(declaration.name && isIdentifier(declaration.name)) { + for (const { target, name } of collectTsPlusStaticTags(declaration)) { + getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); + if (!staticCache.has(target)) { + staticCache.set(target, new Map()); } + const map = staticCache.get(target)!; + map.set(name, () => { + if (staticFunctionCache.has(target)) { + const resolvedMap = staticFunctionCache.get(target)!; + if (resolvedMap.has(name)) { + return resolvedMap.get(name)!; + } + } + else { + staticFunctionCache.set(target, new Map()); + } + const resolvedMap = staticFunctionCache.get(target)!; + const [type, patched] = getTsPlusStaticSymbolForFunctionDeclaration( + file, + declaration.name!.escapedText.toString(), + name, + declaration + ); + const extension = { + patched, + exportName: declaration.name!.escapedText.toString(), + definition: file, + type + }; + resolvedMap.set(name, extension); + return extension; + }); } } - 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); + function cacheTsPlusUnifyFunction(declaration: FunctionDeclaration) { + if(declaration.name && isIdentifier(declaration.name)) { + for (const target of collectTsPlusUnifyTags(declaration)) { + getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); + identityCache.set(target, declaration); + } } - - 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 cacheTsPlusIndexFunction(declaration: FunctionDeclaration) { + if(declaration.name && isIdentifier(declaration.name)) { + for (const target of collectTsPlusIndexTags(declaration)) { + getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); + indexCache.set(target, () => { + if (resolvedIndexCache.has(target)) { + return resolvedIndexCache.get(target); + } + const definition = getSourceFileOfNode(declaration); + const signatures = getSignaturesOfType(getTypeOfNode(declaration), SignatureKind.Call) as Signature[]; + if (signatures[0]) { + resolvedIndexCache.set(target, { declaration, signature: signatures[0], definition, exportName: declaration.name!.escapedText.toString() }); + } + return resolvedIndexCache.get(target)!; + }); } } } - - 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, unescapeLeadingUnderscores(node.name.escapedText), tokenToString(node.keywordToken), "target"); + function cacheTsPlusIndexVariable(declaration: VariableDeclarationWithIdentifier) { + for (const target of collectTsPlusIndexTags(declaration)) { + getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); + indexCache.set(target, () => { + if (resolvedIndexCache.has(target)) { + return resolvedIndexCache.get(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, unescapeLeadingUnderscores(node.name.escapedText), tokenToString(node.keywordToken), "meta"); + const definition = getSourceFileOfNode(declaration); + const signatures = getSignaturesOfType(getTypeOfNode(declaration), SignatureKind.Call) as Signature[]; + if (signatures[0]) { + resolvedIndexCache.set(target, { declaration, signature: signatures[0], definition, exportName: declaration.name!.escapedText.toString() }); } - break; - } - } - - function hasParseDiagnostics(sourceFile: SourceFile): boolean { - return sourceFile.parseDiagnostics.length > 0; - } - - function grammarErrorOnFirstToken(node: Node, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - const span = getSpanOfTokenAtPosition(sourceFile, node.pos); - diagnostics.add(createFileDiagnostic(sourceFile, span.start, span.length, message, ...args)); - return true; - } - return false; - } - - function grammarErrorAtPos(nodeForSourceFile: Node, start: number, length: number, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { - const sourceFile = getSourceFileOfNode(nodeForSourceFile); - if (!hasParseDiagnostics(sourceFile)) { - diagnostics.add(createFileDiagnostic(sourceFile, start, length, message, ...args)); - return true; + return resolvedIndexCache.get(target)!; + }); } - return false; } - - function grammarErrorOnNodeSkippedOn(key: keyof CompilerOptions, node: Node, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - errorSkippedOn(key, node, message, ...args); - return true; + function cacheTsPlusPipeableIndexFunction(declaration: FunctionDeclaration) { + if(declaration.name && isIdentifier(declaration.name)) { + for (const target of collectTsPlusPipeableIndexTags(declaration)) { + getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); + indexCache.set(target, () => { + if (resolvedIndexCache.has(target)) { + return resolvedIndexCache.get(target); + } + const definition = getSourceFileOfNode(declaration); + const exportName = declaration.name!.escapedText.toString(); + const typeAndSignatures = getTsPlusFluentSignatureForPipeableFunction(definition, exportName, exportName, declaration); + if (typeAndSignatures && typeAndSignatures[1][0]) { + resolvedIndexCache.set(target, { declaration, signature: typeAndSignatures[1][0], definition, exportName }); + return resolvedIndexCache.get(target); + } + }) + } } - return false; } - - function grammarErrorOnNode(node: Node, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - diagnostics.add(createDiagnosticForNode(node, message, ...args)); - return true; + function cacheTsPlusPipeableIndexVariable(declaration: VariableDeclarationWithIdentifier) { + for (const target of collectTsPlusPipeableIndexTags(declaration)) { + getTsPlusSourceFileCache(target).add(getSourceFileOfNode(declaration)); + indexCache.set(target, () => { + if (resolvedIndexCache.has(target)) { + return resolvedIndexCache.get(target); + } + const definition = getSourceFileOfNode(declaration); + const exportName = declaration.name!.escapedText.toString(); + const typeAndSignatures = getTsPlusFluentSignatureForPipeableVariableDeclaration(definition, exportName, exportName, declaration as VariableDeclarationWithFunction | VariableDeclarationWithFunctionType); + if (typeAndSignatures && typeAndSignatures[1][0]) { + resolvedIndexCache.set(target, { declaration, signature: typeAndSignatures[1][0], definition, exportName }); + return resolvedIndexCache.get(target); + } + }) } - 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 collectTsPlusSymbols(file: SourceFile): void { + for (const declaration of file.tsPlusContext.type) { + cacheTsPlusType(declaration); } - } - - function checkGrammarConstructorTypeAnnotation(node: ConstructorDeclaration) { - const type = node.type || getEffectiveReturnTypeNode(node); - if (type) { - return grammarErrorOnNode(type, Diagnostics.Type_annotation_cannot_appear_on_a_constructor_declaration); + for (const declaration of file.tsPlusContext.companion) { + cacheTsPlusCompanion(declaration); } - } - - function checkGrammarProperty(node: PropertyDeclaration | PropertySignature) { - if (isComputedPropertyName(node.name) && isBinaryExpression(node.name.expression) && node.name.expression.operatorToken.kind === SyntaxKind.InKeyword) { - return grammarErrorOnNode(node.parent.members[0], Diagnostics.A_mapped_type_may_not_declare_properties_or_methods); + for (const declaration of file.tsPlusContext.fluent) { + if (isFunctionDeclaration(declaration)) { + cacheTsPlusFluentFunction(file, declaration); + } + else { + cacheTsPlusFluentVariable(file, declaration); + } } - 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); + for (const declaration of file.tsPlusContext.pipeable) { + if (isFunctionDeclaration(declaration)) { + cacheTsPlusPipeableFunction(file, declaration); } - if (checkGrammarForInvalidDynamicName(node.name, Diagnostics.A_computed_property_name_in_a_class_property_declaration_must_have_a_simple_literal_type_or_a_unique_symbol_type)) { - return true; + else { + cacheTsPlusPipeableVariable(file, declaration); } - if (languageVersion < ScriptTarget.ES2015 && isPrivateIdentifier(node.name)) { - return grammarErrorOnNode(node.name, Diagnostics.Private_identifiers_are_only_available_when_targeting_ECMAScript_2015_and_higher); + } + for (const declaration of file.tsPlusContext.operator) { + if (isFunctionDeclaration(declaration)) { + cacheTsPlusOperatorFunction(file, declaration); } - if (languageVersion < ScriptTarget.ES2015 && isAutoAccessorPropertyDeclaration(node)) { - return grammarErrorOnNode(node.name, Diagnostics.Properties_with_the_accessor_modifier_are_only_available_when_targeting_ECMAScript_2015_and_higher); + else { + cacheTsPlusOperatorVariable(file, declaration); } - if (isAutoAccessorPropertyDeclaration(node) && checkGrammarForInvalidQuestionMark(node.questionToken, Diagnostics.An_accessor_property_cannot_be_declared_optional)) { - return true; + } + for (const declaration of file.tsPlusContext.pipeableOperator) { + if (isFunctionDeclaration(declaration)) { + cacheTsPlusPipeableOperatorFunction(file, declaration); + } + else { + cacheTsPlusPipeableOperatorVariable(file, declaration); } } - 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; + for (const declaration of file.tsPlusContext.static) { + if (isFunctionDeclaration(declaration)) { + cacheTsPlusStaticFunction(file, declaration); } - - // Interfaces cannot contain property declarations - Debug.assertNode(node, isPropertySignature); - if (node.initializer) { - return grammarErrorOnNode(node.initializer, Diagnostics.An_interface_property_cannot_have_an_initializer); + else { + cacheTsPlusStaticVariable(file, declaration); } } - else if (isTypeLiteralNode(node.parent)) { - 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; + for (const declaration of file.tsPlusContext.getter) { + if (isFunctionDeclaration(declaration)) { + cacheTsPlusGetterFunction(file, declaration); } - // Type literals cannot contain property declarations - Debug.assertNode(node, isPropertySignature); - if (node.initializer) { - return grammarErrorOnNode(node.initializer, Diagnostics.A_type_literal_property_cannot_have_an_initializer); + else { + cacheTsPlusGetterVariable(file, declaration); } } - - if (node.flags & NodeFlags.Ambient) { - checkAmbientInitializer(node); + for (const declaration of file.tsPlusContext.unify) { + cacheTsPlusUnifyFunction(declaration); } - - if (isPropertyDeclaration(node) && node.exclamationToken && (!isClassLike(node.parent) || !node.type || node.initializer || - node.flags & NodeFlags.Ambient || isStatic(node) || hasAbstractModifier(node))) { - 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); + for (const declaration of file.tsPlusContext.index) { + if (isFunctionDeclaration(declaration)) { + cacheTsPlusIndexFunction(declaration); + } + else { + cacheTsPlusIndexVariable(declaration); + } } - } - - 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; + for (const declaration of file.tsPlusContext.pipeableIndex) { + if (isFunctionDeclaration(declaration)) { + cacheTsPlusPipeableIndexFunction(declaration); + } + else { + cacheTsPlusPipeableIndexVariable(declaration); + } } - - 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; - } + function fillTsPlusLocalScope(file: SourceFile) { + const derivationRules = tsPlusWorldScope.rules; + if (file.symbol) { + const exports = file.symbol.exports; + if (exports) { + exports.forEach((exportSymbol) => { + if (exportSymbol.valueDeclaration) { + const declaration = exportSymbol.valueDeclaration; + if (isTsPlusImplicit(declaration)) { + indexInScope(exportSymbol); + } + else if((isFunctionDeclaration(declaration) || isVariableDeclaration(declaration)) && declaration.tsPlusDeriveTags) { + for (const tag of declaration.tsPlusDeriveTags) { + const match = tag.match(/^derive ([^<]*)<([^>]*)> (.*)$/); + if (match) { + const [, typeTag, paramActions, rest] = match; + const [priority, ...options] = rest.split(" "); + if (!derivationRules.has(typeTag)) { + derivationRules.set(typeTag, { lazyRule: void 0, rules: [] }); + } + derivationRules.get(typeTag)?.rules.push([ + { typeTag, paramActions: paramActions.split(",").map((s) => s.trim()) }, + Number.parseFloat(priority), + declaration, + new Set(options) + ]); + } else { + const match = tag.match(/^derive ([^<]*) lazy$/); + if (match) { + const [, typeTag] = match; + if (!derivationRules.has(typeTag)) { + derivationRules.set(typeTag, { lazyRule: void 0, rules: [] }); + } + derivationRules.get(typeTag)!.lazyRule = declaration; + } + } + } + } + } + }); } } - 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)); - } + function initTsPlusTypeCheckerImplicits() { + for (const file of host.getSourceFiles()) { + fillTsPlusLocalScope(file); } - return false; } - - function checkGrammarNumericLiteral(node: NumericLiteral) { - // Realism (size) checking - // We should test against `getTextOfNode(node)` rather than `node.text`, because `node.text` for large numeric literals can contain "." - // e.g. `node.text` for numeric literal `1100000000000000000000` is `1.1e21`. - const isFractional = getTextOfNode(node).indexOf(".") !== -1; - const isScientific = node.numericLiteralFlags & TokenFlags.Scientific; - - // Scientific notation (e.g. 2e54 and 1e00000000010) can't be converted to bigint - // Fractional numbers (e.g. 9000000000000000.001) are inherently imprecise anyway - if (isFractional || isScientific) { + function postInitTsPlusTypeChecker() { + tsPlusDebug && console.time("initTsPlusTypeChecker build dependency tree") + typeSymbolCache.forEach((tags, symbol) => { + forEach(symbol.declarations, (declaration) => { + const source = getSourceFileOfNode(declaration); + const set = tsPlusFilesFinal.has(source) ? tsPlusFilesFinal.get(source)! : (tsPlusFilesFinal.set(source, new Set()), tsPlusFilesFinal.get(source)!); + forEach(tags, (tag) => { + tsPlusFiles.get(tag)?.forEach((dep) => { + set.add(dep); + }) + }) + }) + }) + tsPlusDebug && console.timeEnd("initTsPlusTypeChecker build dependency tree") + } + function initTsPlusTypeChecker() { + if (compilerOptions.tsPlusEnabled === false) { return; } - - // Here `node` is guaranteed to be a numeric literal representing an integer. - // We need to judge whether the integer `node` represents is <= 2 ** 53 - 1, which can be accomplished by comparing to `value` defined below because: - // 1) when `node` represents an integer <= 2 ** 53 - 1, `node.text` is its exact string representation and thus `value` precisely represents the integer. - // 2) otherwise, although `node.text` may be imprecise string representation, its mathematical value and consequently `value` cannot be less than 2 ** 53, - // thus the result of the predicate won't be affected. - const value = +node.text; - if (value <= 2 ** 53 - 1) { - return; + tsPlusDebug && console.time("initTsPlusTypeChecker caches") + fileMap.map = getFileMap(host.getCompilerOptions(), host); + fluentCache.clear(); + unresolvedFluentCache.clear(); + unresolvedPipeableCache.clear(); + unresolvedPipeableOperatorCache.clear(); + operatorCache.clear(); + typeSymbolCache.clear(); + typeHashCache.clear(); + staticFunctionCache.clear(); + staticValueCache.clear(); + unresolvedStaticCache.clear(); + identityCache.clear(); + tsPlusFiles.clear(); + tsPlusFilesFinal.clear(); + getterCache.clear(); + callCache.clear(); + indexCache.clear(); + resolvedIndexCache.clear(); + indexAccessExpressionCache.clear(); + inheritanceSymbolCache.clear(); + tsPlusWorldScope.implicits.clear(); + tsPlusWorldScope.rules.clear(); + tsPlusDebug && console.timeEnd("initTsPlusTypeChecker caches") + tsPlusDebug && console.time("initTsPlusTypeChecker collect") + for (const file of host.getSourceFiles()) { + collectTsPlusSymbols(file); } + tsPlusDebug && console.timeEnd("initTsPlusTypeChecker collect") + tsPlusDebug && console.time("initTsPlusTypeChecker joinining signatures") - 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)); - } + const unresolvedUnionInheritance = new Map() + const unresolvedInheritance = new Map>() - 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; + unresolvedTypeDeclarations.forEach((declaration) => { + const type = getTypeOfNode(declaration); + for (const typeTag of collectTsPlusTypeTags(declaration)) { + if (type === globalStringType) { + addToTypeSymbolCache(tsplusStringPrimitiveSymbol, typeTag, "after"); + } + if (type === globalNumberType) { + addToTypeSymbolCache(tsplusNumberPrimitiveSymbol, typeTag, "after"); + } + if (type === globalBooleanType) { + addToTypeSymbolCache(tsplusBooleanPrimitiveSymbol, typeTag, "after"); + } + if (type === getGlobalBigIntType()) { + addToTypeSymbolCache(tsplusBigIntPrimitiveSymbol, typeTag, "after"); + } + if (type === globalFunctionType) { + addToTypeSymbolCache(tsplusFunctionPrimitiveSymbol, typeTag, "after"); + } + if (type === globalObjectType) { + addToTypeSymbolCache(tsplusObjectPrimitiveSymbol, typeTag, "after"); + } + if (type === globalArrayType) { + addToTypeSymbolCache(tsplusArraySymbol, typeTag, "after"); + } + if (type === globalReadonlyArrayType) { + addToTypeSymbolCache(tsplusReadonlyArraySymbol, typeTag, "after"); + } + if (type.symbol) { + addToTypeSymbolCache(type.symbol, typeTag, "after"); + if ((isInterfaceDeclaration(declaration) || isClassDeclaration(declaration)) && declaration.heritageClauses) { + unresolvedInheritance.set(type.symbol, declaration.heritageClauses); + } + } + if (type.aliasSymbol) { + addToTypeSymbolCache(type.aliasSymbol, typeTag, "after"); + } + if (type.flags & TypeFlags.Union) { + unresolvedUnionInheritance.set(type, (type as UnionType).types) + } + if (type.flags & TypeFlags.UnionOrIntersection) { + const types = (type as UnionOrIntersectionType).types; + for (const member of types) { + if (member.symbol) { + addToTypeSymbolCache(member.symbol, typeTag, "before"); + } + if (member.aliasSymbol) { + addToTypeSymbolCache(member.aliasSymbol, typeTag, "before"); + } + } } } - } - return false; - } - - function grammarErrorAfterFirstToken(node: Node, message: DiagnosticMessage, ...args: DiagnosticArguments): boolean { - const sourceFile = getSourceFileOfNode(node); - if (!hasParseDiagnostics(sourceFile)) { - const span = getSpanOfTokenAtPosition(sourceFile, node.pos); - diagnostics.add(createFileDiagnostic(sourceFile, textSpanEnd(span), /*length*/ 0, message, ...args)); - return true; - } - return false; - } + }) + unresolvedTypeDeclarations.clear(); - 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); + unresolvedCompanionDeclarations.forEach((declaration) => { + const tags = collectTsPlusCompanionTags(declaration); + const type = getTypeOfNode(declaration) + if (type.symbol) { + for (const companionTag of tags) { + getTsPlusSourceFileCache(companionTag).add(getSourceFileOfNode(declaration)); + addToCompanionSymbolCache(type.symbol, companionTag); } - }); - } - 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); - } - if (node.isTypeOnly && node.namedBindings?.kind === SyntaxKind.NamedImports) { - return checkGrammarNamedImportsOrExports(node.namedBindings); - } - return false; - } - - function checkGrammarNamedImportsOrExports(namedBindings: NamedImportsOrExports): boolean { - return !!forEach(namedBindings.elements, specifier => { - if (specifier.isTypeOnly) { - return grammarErrorOnFirstToken( - specifier, - specifier.kind === SyntaxKind.ImportSpecifier - ? Diagnostics.The_type_modifier_cannot_be_used_on_a_named_import_when_import_type_is_used_on_its_import_statement - : Diagnostics.The_type_modifier_cannot_be_used_on_a_named_export_when_export_type_is_used_on_its_export_statement); } - }); - } - - function checkGrammarImportCallExpression(node: ImportCall): boolean { - if (compilerOptions.verbatimModuleSyntax && moduleKind === ModuleKind.CommonJS) { - return grammarErrorOnNode(node, Diagnostics.ESM_syntax_is_not_allowed_in_a_CommonJS_module_when_verbatimModuleSyntax_is_enabled); - } - - if (moduleKind === ModuleKind.ES2015) { - return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_are_only_supported_when_the_module_flag_is_set_to_es2020_es2022_esnext_commonjs_amd_system_umd_node16_or_nodenext); - } - - if (node.typeArguments) { - return grammarErrorOnNode(node, Diagnostics.This_use_of_import_is_invalid_import_calls_can_be_written_but_they_must_have_parentheses_and_cannot_have_type_arguments); - } - - const nodeArguments = node.arguments; - if (moduleKind !== ModuleKind.ESNext && moduleKind !== ModuleKind.NodeNext && moduleKind !== ModuleKind.Node16) { - // We are allowed trailing comma after proposal-import-assertions. - checkGrammarForDisallowedTrailingComma(nodeArguments); + if (type.aliasSymbol) { + for (const companionTag of tags) { + getTsPlusSourceFileCache(companionTag).add(getSourceFileOfNode(declaration)); + addToCompanionSymbolCache(type.aliasSymbol, companionTag); + } + } + }) + unresolvedCompanionDeclarations.clear() - if (nodeArguments.length > 1) { - const assertionArgument = nodeArguments[1]; - return grammarErrorOnNode(assertionArgument, Diagnostics.Dynamic_imports_only_support_a_second_argument_when_the_module_option_is_set_to_esnext_node16_or_nodenext); + unresolvedStaticCache.forEach((map, typeName) => { + if (!staticCache.has(typeName)) { + staticCache.set(typeName, new Map()); } - } + const staticMap = staticCache.get(typeName)!; + map.forEach(({ target, declaration, name, definition, exportName }) => { + staticMap.set(name, () => { + if (staticValueCache.has(target)) { + const resolvedMap = staticValueCache.get(target)!; + if (resolvedMap.has(name)) { + return resolvedMap.get(name)!; + } + } + else { + staticValueCache.set(target, new Map()); + } + const resolvedMap = staticValueCache.get(typeName)!; + const nameSymbol = getSymbolAtLocation(declaration.name!); + if (nameSymbol) { + const patched = createTsPlusStaticValueSymbol(name, declaration, nameSymbol); + if (isClassDeclaration(declaration)) { + const companionTags = collectTsPlusCompanionTags(declaration); + for (const companionTag of companionTags) { + getTsPlusSourceFileCache(companionTag).add(getSourceFileOfNode(declaration)); + addToCompanionSymbolCache(patched, companionTag); + } + } + const type = getTypeOfSymbol(patched); + // @ts-expect-error + type.tsPlusSymbol = patched; + const extension = { + patched, + definition, + exportName, + type + } + resolvedMap.set(name, extension); + return extension; + } + }) + }) + }) + unresolvedFluentCache.forEach((map, typeName) => { + if (!fluentCache.has(typeName)) { + fluentCache.set(typeName, new Map()); + } + const fluentMap = fluentCache.get(typeName)!; + map.forEach(({ name, definition }) => { + fluentMap.set(name, () => { + if (resolvedFluentCache.has(typeName)) { + const resolvedMap = resolvedFluentCache.get(typeName)!; + if (resolvedMap.has(name)) { + return resolvedMap.get(name)!; + } + } + const allTypes: { type: Type, signatures: readonly TsPlusSignature[] }[] = []; + const prioritizedSignaturesMap = new Map () - if (nodeArguments.length === 0 || nodeArguments.length > 2) { - return grammarErrorOnNode(node, Diagnostics.Dynamic_imports_can_only_accept_a_module_specifier_and_an_optional_assertion_as_arguments); - } + definition.forEach(({ declaration, definition, exportName, priority }) => { + if (isFunctionDeclaration(declaration)) { + const typeAndSignatures = getTsPlusFluentSignaturesForFunctionDeclaration(definition, exportName, declaration); + if (typeAndSignatures) { + const [type, signatures] = typeAndSignatures; + if (prioritizedSignaturesMap.has(priority)) { + prioritizedSignaturesMap.get(priority)!.push(...signatures) + } + else { + prioritizedSignaturesMap.set(priority, signatures); + } + allTypes.push({ type, signatures }); + } + } + else { + const typeAndSignatures = getTsPlusFluentSignaturesForVariableDeclaration(definition, exportName, declaration); + if (typeAndSignatures) { + const [type, signatures] = typeAndSignatures; + if (prioritizedSignaturesMap.has(priority)) { + prioritizedSignaturesMap.get(priority)!.push(...signatures) + } + else { + prioritizedSignaturesMap.set(priority, signatures); + } + allTypes.push({ type, signatures }); + } + } + }); - // 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. - const spreadElement = find(nodeArguments, isSpreadElement); - if (spreadElement) { - return grammarErrorOnNode(spreadElement, Diagnostics.Argument_of_dynamic_import_cannot_be_spread_element); - } - return false; - } + const prioritizedSignatures: [number, TsPlusSignature[]][] = [] + prioritizedSignaturesMap.forEach((signatures, priority) => { + prioritizedSignatures.push([priority, signatures]) + }) + prioritizedSignatures.sort((x, y) => x[0] > y[0] ? 1 : x[0] < y[0] ? -1 : 0) - 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; + const allSignatures = prioritizedSignatures.flatMap((signatures) => signatures[1]) + + if (allSignatures.length === 0) { + return undefined; } - if (overlapObjFlags & ObjectFlags.Anonymous) { - return !!(source as AnonymousType).aliasSymbol && (source as AnonymousType).aliasSymbol === (target as AnonymousType).aliasSymbol; + const symbol = createTsPlusFluentSymbol(name, allSignatures); + const type = createAnonymousType(symbol, emptySymbols, allSignatures, [], []); + const extension: TsPlusFluentExtension = { + patched: createSymbolWithType(symbol, type), + signatures: allSignatures, + types: allTypes + }; + if (!resolvedFluentCache.has(typeName)) { + resolvedFluentCache.set(typeName, new Map()); } - } - return false; + const resolvedMap = resolvedFluentCache.get(typeName)!; + resolvedMap.set(name, extension); + return extension; + }); }); - } - } + }); + unresolvedFluentCache.clear(); + unresolvedPipeableCache.forEach((map, typeName) => { + if (!fluentCache.has(typeName)) { + fluentCache.set(typeName, new Map()); + } + const fluentMap = fluentCache.get(typeName)!; + map.forEach(({ name, definition }) => { + if (!fluentMap.has(name)) { + fluentMap.set(name, () => { + if (resolvedFluentCache.has(typeName)) { + const resolvedMap = resolvedFluentCache.get(typeName)!; + if (resolvedMap.has(name)) { + return resolvedMap.get(name)!; + } + } + const allTypes: { type: Type, signatures: readonly TsPlusSignature[] }[] = []; + const prioritizedSignaturesMap = new Map () - function findBestTypeForObjectLiteral(source: Type, unionTarget: UnionOrIntersectionType) { - if (getObjectFlags(source) & ObjectFlags.ObjectLiteral && someType(unionTarget, isArrayLikeType)) { - return find(unionTarget.types, t => !isArrayLikeType(t)); - } - } + definition.forEach(({ priority, getTypeAndSignatures }) => { + const typeAndSignatures = getTypeAndSignatures(); + if (typeAndSignatures) { + const [type, signatures] = typeAndSignatures; + if (prioritizedSignaturesMap.has(priority)) { + prioritizedSignaturesMap.get(priority)!.push(...signatures) + } + else { + prioritizedSignaturesMap.set(priority, signatures); + } + allTypes.push({ type, signatures }); + } + }); - 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); - } - } + const prioritizedSignatures: [number, TsPlusSignature[]][] = [] + prioritizedSignaturesMap.forEach((signatures, priority) => { + prioritizedSignatures.push([priority, signatures]) + }) + prioritizedSignatures.sort((x, y) => x[0] > y[0] ? 1 : x[0] < y[0] ? -1 : 0) - function findMostOverlappyType(source: Type, unionTarget: UnionOrIntersectionType) { - let bestMatch: Type | undefined; - if (!(source.flags & (TypeFlags.Primitive | TypeFlags.InstantiablePrimitive))) { - let matchingCount = 0; - for (const target of unionTarget.types) { - if (!(target.flags & (TypeFlags.Primitive | TypeFlags.InstantiablePrimitive))) { - const overlap = getIntersectionType([getIndexType(source), getIndexType(target)]); - if (overlap.flags & TypeFlags.Index) { - // perfect overlap of keys - return target; - } - else if (isUnitType(overlap) || 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 = overlap.flags & TypeFlags.Union ? countWhere((overlap as UnionType).types, isUnitType) : 1; - if (len >= matchingCount) { - bestMatch = target; - matchingCount = len; + const allSignatures = prioritizedSignatures.flatMap((signatures) => signatures[1]) + + if (allSignatures.length === 0) { + return undefined; } - } + const symbol = createTsPlusFluentSymbol(name, allSignatures); + const type = createAnonymousType(symbol, emptySymbols, allSignatures, [], []); + const extension: TsPlusFluentExtension = { + patched: createSymbolWithType(symbol, type), + signatures: allSignatures, + types: allTypes + }; + if (!resolvedFluentCache.has(typeName)) { + resolvedFluentCache.set(typeName, new Map()); + } + const resolvedMap = resolvedFluentCache.get(typeName)!; + resolvedMap.set(name, extension); + return extension; + }); } + }); + }); + unresolvedPipeableCache.clear(); + unresolvedPipeableOperatorCache.forEach((map, typeName) => { + if (!operatorCache.has(typeName)) { + operatorCache.set(typeName, new Map()); } - } - return bestMatch; - } + const cache = operatorCache.get(typeName)!; + map.forEach((member, funcName) => { + if (!cache.has(funcName)) { + cache.set(funcName, []); + } + member.definition.forEach(({ priority, getTypeAndSignatures, exportName, definition }) => { + const [memberType, memberSignatures] = getTypeAndSignatures(); + const symbol = createSymbol(SymbolFlags.Function, funcName as __String); + symbol.declarations = memberSignatures.map((sig) => sig.declaration!); + getSymbolLinks(symbol).type = memberType; + cache.get(funcName)!.push({ + patched: symbol, + exportName, + definition, + priority + }) + }) + }) + }) + unresolvedPipeableOperatorCache.clear(); + operatorCache.forEach((map) => { + map.forEach((extensions) => { + extensions.sort(({ priority: x }, { priority: y }) => x > y ? 1 : x < y ? -1 : 0) + }) + }) - 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; - } + unresolvedUnionInheritance.forEach((types, type) => { + tryCacheUnionInheritance(types, type); + }); + unresolvedUnionInheritance.clear(); - // 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) { - if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) { - const match = getMatchingUnionConstituentForType(target as UnionType, source); - if (match) { - return match; - } - const sourceProperties = getPropertiesOfType(source); - if (sourceProperties) { - const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); - if (sourcePropertiesFiltered) { - const discriminated = discriminateTypeByDiscriminableItems(target as UnionType, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo); - if (discriminated !== target) { - return discriminated; - } - } - } - } - return undefined; - } + unresolvedInheritance.forEach((heritageClauses, symbol) => { + tryCacheTsPlusInheritance(symbol, heritageClauses); + }); + unresolvedInheritance.clear() - function getEffectivePropertyNameForPropertyNameNode(node: PropertyName) { - const name = getPropertyNameForPropertyNameNode(node); - return name ? name : - isComputedPropertyName(node) && isEntityNameExpression(node.expression) ? tryGetNameFromEntityNameExpression(node.expression) : undefined; + tsPlusDebug && console.timeEnd("initTsPlusTypeChecker joinining signatures") + tsPlusDebug && console.time("initTsPlusTypeChecker implicits") + initTsPlusTypeCheckerImplicits(); + tsPlusDebug && console.timeEnd("initTsPlusTypeChecker implicits") + postInitTsPlusTypeChecker(); } + // + // TSPLUS END + // } function isNotAccessor(declaration: Declaration): boolean { @@ -53480,7 +53525,9 @@ class SymbolTrackerImpl implements SymbolTracker { } } -// TSPLUS EXTENSION BEGIN +// +// TSPLUS START +// export function isTsPlusSymbol(symbol: Symbol): symbol is TsPlusSymbol { return 'tsPlusTag' in symbol; } @@ -53606,5 +53653,6 @@ export function getDeclarationForTsPlus(checker: TypeChecker, node: Node, symbol } } } - -// TSPLUS EXTENSION END +// +// TSPLUS END +// diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1bf32af2e3..26df603c82 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -924,7 +924,14 @@ export interface Node extends ReadonlyTextRange { // `locals` and `nextContainer` have been moved to `LocalsContainer` // `flowNode` has been moved to `FlowContainer` // see: https://github.com/microsoft/TypeScript/pull/51682 + + // + // TSPLUS START + // /** @internal */ tsPlusName?: string; + // + // TSPLUS END + // } export interface JSDocContainer extends Node { @@ -1844,7 +1851,13 @@ export type SignatureDeclaration = export interface CallSignatureDeclaration extends SignatureDeclarationBase, TypeElement, LocalsContainer { readonly kind: SyntaxKind.CallSignature; + // + // TSPLUS START + // tsPlusMacroTags?: string[]; + // + // TSPLUS END + // } export interface ConstructSignatureDeclaration extends SignatureDeclarationBase, TypeElement, LocalsContainer { @@ -1860,6 +1873,9 @@ export interface VariableDeclaration extends NamedDeclaration, JSDocContainer { readonly exclamationToken?: ExclamationToken; // Optional definite assignment assertion readonly type?: TypeNode; // Optional type annotation readonly initializer?: Expression; // Optional initializer + // + // TSPLUS START + // isTsPlusImplicit: boolean; tsPlusDeriveTags?: string[]; tsPlusPipeableTags?: TsPlusPrioritizedExtensionTag[]; @@ -1873,6 +1889,9 @@ export interface VariableDeclaration extends NamedDeclaration, JSDocContainer { tsPlusIndexTags?: string[]; tsPlusPipeableIndexTags?: string[]; tsPlusValidFluent?: boolean + // + // TSPLUS END + // } /** @internal */ @@ -2079,7 +2098,9 @@ export interface FunctionDeclaration extends FunctionLikeDeclarationBase, Declar readonly modifiers?: NodeArray; readonly name?: Identifier; readonly body?: FunctionBody; - // TSPLUS BEGIN + // + // TSPLUS START + // readonly tsPlusDeriveTags?: string[]; readonly tsPlusPipeableTags?: TsPlusPrioritizedExtensionTag[]; readonly tsPlusFluentTags?: TsPlusPrioritizedExtensionTag[]; @@ -2092,7 +2113,9 @@ export interface FunctionDeclaration extends FunctionLikeDeclarationBase, Declar readonly tsPlusIndexTags?: string[]; readonly tsPlusPipeableIndexTags?: string[]; readonly tsPlusValidFluent?: boolean + // // TSPLUS END + // } export interface MethodSignature extends SignatureDeclarationBase, TypeElement, LocalsContainer { @@ -3582,13 +3605,17 @@ export interface ClassDeclaration extends ClassLikeDeclarationBase, DeclarationS /** May be undefined in `export default class { ... }`. */ readonly name?: Identifier; + // // TSPLUS START + // readonly tsPlusTypeTags?: string[]; readonly tsPlusCompanionTags?: string[]; readonly tsPlusStaticTags?: TsPlusExtensionTag[]; readonly tsPlusDeriveTags?: string[]; readonly tsPlusNoInheritTags?: string[]; + // // TSPLUS END + // } export interface ClassExpression extends ClassLikeDeclarationBase, PrimaryExpression { @@ -3619,12 +3646,16 @@ export interface InterfaceDeclaration extends DeclarationStatement, JSDocContain readonly typeParameters?: NodeArray; readonly heritageClauses?: NodeArray; readonly members: NodeArray; - // TSPLUS BEGIN + // + // TSPLUS START + // readonly tsPlusTypeTags?: string[]; readonly tsPlusDeriveTags?: string[]; readonly tsPlusNoInheritTags?: string[]; readonly tsPlusCompanionTags?: string[]; + // // TSPLUS END + // } export interface HeritageClause extends Node { @@ -3640,11 +3671,15 @@ export interface TypeAliasDeclaration extends DeclarationStatement, JSDocContain readonly name: Identifier; readonly typeParameters?: NodeArray; readonly type: TypeNode; - // TSPLUS BEGIN + // + // TSPLUS START + // readonly tsPlusTypeTags?: string[]; readonly tsPlusCompanionTags?: string[]; readonly tsPlusNoInheritTags?: string[]; + // // TSPLUS END + // } export interface EnumMember extends NamedDeclaration, JSDocContainer { @@ -3752,9 +3787,13 @@ export interface ImportDeclaration extends Statement { /** If this is not a StringLiteral it will be a grammar error. */ readonly moduleSpecifier: Expression; readonly assertClause?: AssertClause; - // TSPLUS BEGIN + // + // TSPLUS START + // readonly isTsPlusGlobal: boolean; + // // TSPLUS END + // } export type NamedImportBindings = @@ -4004,591 +4043,313 @@ export interface JSDocTag extends Node { readonly comment?: string | NodeArray; } -export interface TsPlusJSDocDeriveTag extends JSDocTag { - readonly parent: JSDoc | JSDocTypeLiteral; - readonly tagName: Identifier; - readonly comment: `derive ${string}` +export interface JSDocLink extends Node { + readonly kind: SyntaxKind.JSDocLink; + readonly name?: EntityName | JSDocMemberName; + text: string; } -export interface TsPlusJSDocImplicitTag extends JSDocTag { - readonly parent: JSDoc | JSDocTypeLiteral; - readonly tagName: Identifier; - readonly comment: `implicit` +export interface JSDocLinkCode extends Node { + readonly kind: SyntaxKind.JSDocLinkCode; + readonly name?: EntityName | JSDocMemberName; + text: string; } -export interface TsPlusJSDocTypeTag extends JSDocTag { - readonly parent: JSDoc | JSDocTypeLiteral; - readonly tagName: Identifier; - readonly comment: `type ${string}` +export interface JSDocLinkPlain extends Node { + readonly kind: SyntaxKind.JSDocLinkPlain; + readonly name?: EntityName | JSDocMemberName; + text: string; } -export interface TsPlusJSDocUnifyTag extends JSDocTag { - readonly parent: JSDoc | JSDocTypeLiteral; - readonly tagName: Identifier; - readonly comment: `unify ${string}` +export type JSDocComment = JSDocText | JSDocLink | JSDocLinkCode | JSDocLinkPlain; + +export interface JSDocText extends Node { + readonly kind: SyntaxKind.JSDocText; + text: string; } -export interface TsPlusJSDocIndexTag extends JSDocTag { - readonly parent: JSDoc | JSDocTypeLiteral; - readonly tagName: Identifier; - readonly comment: `index ${string}` +export interface JSDocUnknownTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocTag; } -export interface TsPlusJSDocFluentTag extends JSDocTag { - readonly parent: JSDoc | JSDocTypeLiteral; - readonly tagName: Identifier; - readonly comment: `fluent ${string} ${string}` +/** + * Note that `@extends` is a synonym of `@augments`. + * Both tags are represented by this interface. + */ +export interface JSDocAugmentsTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocAugmentsTag; + readonly class: ExpressionWithTypeArguments & { readonly expression: Identifier | PropertyAccessEntityNameExpression }; } -export interface TsPlusJSDocGlobalTag extends JSDocTag { - readonly parent: JSDoc | JSDocTypeLiteral; - readonly tagName: Identifier; - readonly comment: `global` +export interface JSDocImplementsTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocImplementsTag; + readonly class: ExpressionWithTypeArguments & { readonly expression: Identifier | PropertyAccessEntityNameExpression }; } -export interface TsPlusExtensionTag { - readonly tagType: string; - readonly target: string; - readonly name: string; +export interface JSDocAuthorTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocAuthorTag; } -export interface TsPlusPrioritizedExtensionTag extends TsPlusExtensionTag { - readonly priority: number; +export interface JSDocDeprecatedTag extends JSDocTag { + kind: SyntaxKind.JSDocDeprecatedTag; } -export interface TsPlusJSDocPipeableTag extends JSDocTag { - readonly parent: JSDoc | JSDocTypeLiteral; - readonly tagName: Identifier; - readonly comment: `pipeable ${string} ${string}` +export interface JSDocClassTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocClassTag; } -export interface TsPlusJSDocGetterTag extends JSDocTag { - readonly parent: JSDoc | JSDocTypeLiteral; - readonly tagName: Identifier; - readonly comment: `getter ${string} ${string}` +export interface JSDocPublicTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocPublicTag; } -export interface TsPlusJSDocStaticTag extends JSDocTag { - readonly parent: JSDoc | JSDocTypeLiteral; - readonly tagName: Identifier; - readonly comment: `static ${string} ${string}` +export interface JSDocPrivateTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocPrivateTag; } -export interface TsPlusJSDocOperatorTag extends JSDocTag { - readonly parent: JSDoc | JSDocTypeLiteral; - readonly tagName: Identifier; - readonly comment: `operator ${string} ${string}` +export interface JSDocProtectedTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocProtectedTag; } -export interface TsPlusJSDocMacroTag extends JSDocTag { - readonly parent: JSDoc | JSDocTypeLiteral; - readonly tagName: Identifier; - readonly comment: `macro ${string}` +export interface JSDocReadonlyTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocReadonlyTag; } -export type TsPlusMacroCallExpression = CallExpression & { __tsplus_brand: K }; +export interface JSDocOverrideTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocOverrideTag; +} -export const enum TsPlusSymbolTag { - Fluent = "TsPlusFluentSymbol", - StaticFunction = "TsPlusStaticFunctionSymbol", - StaticValue = "TsPlusStaticValueSymbol", - UnresolvedStatic = "TsPlusUnresolvedStatic", - Getter = "TsPlusGetterSymbol", - GetterVariable = "TsPlusGetterVariableSymbol", - PipeableMacro = "TsPlusPipeableMacroSymbol", - PipeableIdentifier = "TsPlusPipeableSymbol", - PipeableDeclaration = "TsPlusPipeableDeclarationSymbol" +export interface JSDocEnumTag extends JSDocTag, Declaration, LocalsContainer { + readonly kind: SyntaxKind.JSDocEnumTag; + readonly parent: JSDoc; + readonly typeExpression: JSDocTypeExpression; } +export interface JSDocThisTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocThisTag; + readonly typeExpression: JSDocTypeExpression; +} -export interface TsPlusPipeableDeclarationSymbol extends TransientSymbol { - tsPlusTag: TsPlusSymbolTag.PipeableDeclaration - tsPlusDeclaration: FunctionDeclaration | VariableDeclarationWithFunction | VariableDeclarationWithFunctionType; +export interface JSDocTemplateTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocTemplateTag; + readonly constraint: JSDocTypeExpression | undefined; + readonly typeParameters: NodeArray; } -export interface TsPlusPipeableIdentifierSymbol extends TransientSymbol { - tsPlusTag: TsPlusSymbolTag.PipeableIdentifier; - tsPlusDeclaration: FunctionDeclaration | VariableDeclarationWithFunction | VariableDeclarationWithFunctionType; - tsPlusTypeName: string; - tsPlusName: string; - getTsPlusDataFirstType(): Type; +export interface JSDocSeeTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocSeeTag; + readonly name?: JSDocNameReference; } -export interface TsPlusPipeableMacroSymbol extends TransientSymbol { - tsPlusTag: TsPlusSymbolTag.PipeableMacro; - tsPlusDeclaration: VariableDeclaration; - tsPlusDataFirst: FunctionDeclaration | ArrowFunction | FunctionExpression; - tsPlusSourceFile: SourceFile; - tsPlusExportName: string; +export interface JSDocReturnTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocReturnTag; + readonly typeExpression?: JSDocTypeExpression; } -export interface TsPlusFluentSymbol extends TransientSymbol { - tsPlusTag: TsPlusSymbolTag.Fluent; - tsPlusName: string; - tsPlusResolvedSignatures: TsPlusSignature[]; +export interface JSDocTypeTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocTypeTag; + readonly typeExpression: JSDocTypeExpression; } -export type VariableDeclarationWithFunction = Omit & { name: Identifier, initializer: ArrowFunction | FunctionExpression }; +export interface JSDocTypedefTag extends JSDocTag, NamedDeclaration, LocalsContainer { + readonly kind: SyntaxKind.JSDocTypedefTag; + readonly parent: JSDoc; + readonly fullName?: JSDocNamespaceDeclaration | Identifier; + readonly name?: Identifier; + readonly typeExpression?: JSDocTypeExpression | JSDocTypeLiteral; +} -export type VariableDeclarationWithFunctionType = Omit & { name: Identifier, type: FunctionTypeNode }; +export interface JSDocCallbackTag extends JSDocTag, NamedDeclaration, LocalsContainer { + readonly kind: SyntaxKind.JSDocCallbackTag; + readonly parent: JSDoc; + readonly fullName?: JSDocNamespaceDeclaration | Identifier; + readonly name?: Identifier; + readonly typeExpression: JSDocSignature; +} -export type VariableDeclarationWithIdentifier = VariableDeclaration & { name: Identifier }; -export type ClassDeclarationWithIdentifier = ClassDeclaration & { name: Identifier }; +export interface JSDocOverloadTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocOverloadTag; + readonly parent: JSDoc; + readonly typeExpression: JSDocSignature; +} -export interface TsPlusUnresolvedStaticSymbol extends TransientSymbol { - tsPlusTag: TsPlusSymbolTag.UnresolvedStatic - tsPlusDeclaration: VariableDeclaration - tsPlusName: string +export interface JSDocThrowsTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocThrowsTag; + readonly typeExpression?: JSDocTypeExpression; } -export interface TsPlusStaticFunctionSymbol extends TransientSymbol { - tsPlusTag: TsPlusSymbolTag.StaticFunction; - tsPlusDeclaration: FunctionDeclaration | VariableDeclaration; - tsPlusResolvedSignatures: Signature[]; - tsPlusName: string; +export interface JSDocSignature extends JSDocType, Declaration, JSDocContainer, LocalsContainer { + readonly kind: SyntaxKind.JSDocSignature; + readonly typeParameters?: readonly JSDocTemplateTag[]; + readonly parameters: readonly JSDocParameterTag[]; + readonly type: JSDocReturnTag | undefined; } -export interface TsPlusStaticValueSymbol extends TransientSymbol { - tsPlusTag: TsPlusSymbolTag.StaticValue; - tsPlusResolvedSignatures: TsPlusSignature[]; - tsPlusName: string; - tsPlusDeclaration: (VariableDeclaration | ClassDeclaration) & { name: Identifier }; +export interface JSDocPropertyLikeTag extends JSDocTag, Declaration { + readonly parent: JSDoc; + readonly name: EntityName; + readonly typeExpression?: JSDocTypeExpression; + /** Whether the property name came before the type -- non-standard for JSDoc, but Typescript-like */ + readonly isNameFirst: boolean; + readonly isBracketed: boolean; } -export interface TsPlusGetterSymbol extends TransientSymbol { - tsPlusTag: TsPlusSymbolTag.Getter; - tsPlusSelfType: Type; - tsPlusDeclaration: FunctionDeclaration; - tsPlusName: string; +export interface JSDocPropertyTag extends JSDocPropertyLikeTag { + readonly kind: SyntaxKind.JSDocPropertyTag; } -export interface TsPlusGetterVariableSymbol extends TransientSymbol { - tsPlusTag: TsPlusSymbolTag.GetterVariable; - tsPlusDeclaration: VariableDeclaration & { name: Identifier }; - tsPlusSelfType: Type; - tsPlusName: string; -} - -export type TsPlusSymbol = - | TsPlusFluentSymbol - | TsPlusStaticFunctionSymbol - | TsPlusStaticValueSymbol - | TsPlusGetterSymbol - | TsPlusGetterVariableSymbol - | TsPlusPipeableMacroSymbol - | TsPlusPipeableIdentifierSymbol - | TsPlusPipeableDeclarationSymbol; - -export interface TsPlusFluentExtension { - patched: Symbol; - types: { type: Type, signatures: readonly TsPlusSignature[] }[]; - signatures: readonly TsPlusSignature[]; -} - -export interface TsPlusPipeableExtension { - declaration: FunctionDeclaration | VariableDeclarationWithFunction | VariableDeclarationWithFunctionType; - definition: SourceFile; - exportName: string; - typeName: string; - funcName: string; - getTypeAndSignatures(): [Type, TsPlusSignature[]]; -} - -export interface TsPlusUnresolvedStaticExtension { - symbol: Symbol; - declaration: VariableDeclaration | ClassDeclaration; - definition: SourceFile; - target: string; - name: string; - exportName: string; +export interface JSDocParameterTag extends JSDocPropertyLikeTag { + readonly kind: SyntaxKind.JSDocParameterTag; } -export interface TsPlusUnresolvedFluentExtensionDefinition { - declaration: (VariableDeclaration & { name: Identifier }) | FunctionDeclaration; - exportName: string; - definition: SourceFile; - priority: number; +export interface JSDocTypeLiteral extends JSDocType, Declaration { + readonly kind: SyntaxKind.JSDocTypeLiteral; + readonly jsDocPropertyTags?: readonly JSDocPropertyLikeTag[]; + /** If true, then this type literal represents an *array* of its type. */ + readonly isArrayType: boolean; } -export interface TsPlusUnresolvedPipeableExtensionDefinition { - declaration: (VariableDeclaration & { name: Identifier }) | FunctionDeclaration; - exportName: string; - definition: SourceFile; - priority: number; - getTypeAndSignatures(): [Type, TsPlusSignature[]]; +export interface JSDocSatisfiesTag extends JSDocTag { + readonly kind: SyntaxKind.JSDocSatisfiesTag; + readonly typeExpression: JSDocTypeExpression; } -export interface TsPlusUnresolvedPipeableExtension { - definition: Set; - target: string; - name: string; +/** @internal */ +export interface JSDocSatisfiesExpression extends ParenthesizedExpression { + readonly _jsDocSatisfiesExpressionBrand: never; } -export interface TsPlusUnresolvedFluentExtension { - definition: Set; - target: string; - name: string; -} +// NOTE: Ensure this is up-to-date with src/debug/debug.ts +export const enum FlowFlags { + Unreachable = 1 << 0, // Unreachable code + Start = 1 << 1, // Start of flow graph + BranchLabel = 1 << 2, // Non-looping junction + LoopLabel = 1 << 3, // Looping junction + Assignment = 1 << 4, // Assignment + TrueCondition = 1 << 5, // Condition known to be true + FalseCondition = 1 << 6, // Condition known to be false + SwitchClause = 1 << 7, // Switch statement clause + ArrayMutation = 1 << 8, // Potential array mutation + Call = 1 << 9, // Potential assertion call + ReduceLabel = 1 << 10, // Temporarily reduce antecedents of label + Referenced = 1 << 11, // Referenced as antecedent once + Shared = 1 << 12, // Referenced as antecedent more than once -export interface TsPlusStaticFunctionExtension { - patched: Symbol; - definition: SourceFile; - exportName: string; - type: Type; + Label = BranchLabel | LoopLabel, + Condition = TrueCondition | FalseCondition, } -export interface TsPlusStaticValueExtension { - patched: Symbol; - definition: SourceFile; - exportName: string; - type: Type; -} +export type FlowNode = + | FlowStart + | FlowLabel + | FlowAssignment + | FlowCondition + | FlowSwitchClause + | FlowArrayMutation + | FlowCall + | FlowReduceLabel; -export interface TsPlusGetterExtension { - patched: (node: Expression) => TsPlusSymbol | undefined - definition: SourceFile - exportName: string - declaration: FunctionDeclaration | VariableDeclarationWithIdentifier +export interface FlowNodeBase { + flags: FlowFlags; + id?: number; // Node id used by flow type cache in checker } -export interface TsPlusOperatorExtension { - patched: Symbol; - definition: SourceFile; - exportName: string; - priority: number; +// FlowStart represents the start of a control flow. For a function expression or arrow +// function, the node property references the function (which in turn has a flowNode +// property for the containing control flow). +export interface FlowStart extends FlowNodeBase { + node?: FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; } -export interface TsPlusGlobalImport { - declaration: ImportDeclaration; - importSpecifier: ImportSpecifier; - moduleSpecifier: StringLiteral; +// FlowLabel represents a junction with multiple possible preceding control flows. +export interface FlowLabel extends FlowNodeBase { + antecedents: FlowNode[] | undefined; } -export interface TsPlusType extends Type { - tsPlusSymbol: TsPlusSymbol; +// FlowAssignment represents a node that assigns a value to a narrowable reference, +// i.e. an identifier or a dotted name that starts with an identifier or 'this'. +export interface FlowAssignment extends FlowNodeBase { + node: Expression | VariableDeclaration | BindingElement; + antecedent: FlowNode; } -export interface TsPlusSignature extends Signature { - tsPlusTag: "TsPlusSignature"; - tsPlusFile: SourceFile; - tsPlusExportName: string; - tsPlusDeclaration?: Declaration; - tsPlusPipeable?: boolean; - tsPlusOriginal: Signature +export interface FlowCall extends FlowNodeBase { + node: CallExpression; + antecedent: FlowNode; } -export interface TsPlusUniqueIdentifier extends Identifier { - tsPlusUniqueIdentifier: true +// FlowCondition represents a condition that is known to be true or false at the +// node's location in the control flow. +export interface FlowCondition extends FlowNodeBase { + node: Expression; + antecedent: FlowNode; } -export interface JSDocLink extends Node { - readonly kind: SyntaxKind.JSDocLink; - readonly name?: EntityName | JSDocMemberName; - text: string; +export interface FlowSwitchClause extends FlowNodeBase { + switchStatement: SwitchStatement; + clauseStart: number; // Start index of case/default clause range + clauseEnd: number; // End index of case/default clause range + antecedent: FlowNode; } -export interface JSDocLinkCode extends Node { - readonly kind: SyntaxKind.JSDocLinkCode; - readonly name?: EntityName | JSDocMemberName; - text: string; +// FlowArrayMutation represents a node potentially mutates an array, i.e. an +// operation of the form 'x.push(value)', 'x.unshift(value)' or 'x[n] = value'. +export interface FlowArrayMutation extends FlowNodeBase { + node: CallExpression | BinaryExpression; + antecedent: FlowNode; } -export interface JSDocLinkPlain extends Node { - readonly kind: SyntaxKind.JSDocLinkPlain; - readonly name?: EntityName | JSDocMemberName; - text: string; +export interface FlowReduceLabel extends FlowNodeBase { + target: FlowLabel; + antecedents: FlowNode[]; + antecedent: FlowNode; } -export type JSDocComment = JSDocText | JSDocLink | JSDocLinkCode | JSDocLinkPlain; +export type FlowType = Type | IncompleteType; -export interface JSDocText extends Node { - readonly kind: SyntaxKind.JSDocText; - text: string; +// Incomplete types occur during control flow analysis of loops. An IncompleteType +// is distinguished from a regular type by a flags value of zero. Incomplete type +// objects are internal to the getFlowTypeOfReference function and never escape it. +export interface IncompleteType { + flags: TypeFlags | 0; // No flags set + type: Type; // The type marked incomplete } -export interface JSDocUnknownTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocTag; +export interface AmdDependency { + path: string; + name?: string; } /** - * Note that `@extends` is a synonym of `@augments`. - * Both tags are represented by this interface. + * Subset of properties from SourceFile that are used in multiple utility functions */ -export interface JSDocAugmentsTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocAugmentsTag; - readonly class: ExpressionWithTypeArguments & { readonly expression: Identifier | PropertyAccessEntityNameExpression }; -} - -export interface JSDocImplementsTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocImplementsTag; - readonly class: ExpressionWithTypeArguments & { readonly expression: Identifier | PropertyAccessEntityNameExpression }; -} - -export interface JSDocAuthorTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocAuthorTag; -} - -export interface JSDocDeprecatedTag extends JSDocTag { - kind: SyntaxKind.JSDocDeprecatedTag; -} - -export interface JSDocClassTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocClassTag; -} - -export interface JSDocPublicTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocPublicTag; +export interface SourceFileLike { + readonly text: string; + /** @internal */ + lineMap?: readonly number[]; + /** @internal */ + getPositionOfLineAndCharacter?(line: number, character: number, allowEdits?: true): number; } -export interface JSDocPrivateTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocPrivateTag; -} -export interface JSDocProtectedTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocProtectedTag; +/** @internal */ +export interface RedirectInfo { + /** Source file this redirects to. */ + readonly redirectTarget: SourceFile; + /** + * Source file for the duplicate package. This will not be used by the Program, + * but we need to keep this around so we can watch for changes in underlying. + */ + readonly unredirected: SourceFile; } -export interface JSDocReadonlyTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocReadonlyTag; -} +export type ResolutionMode = ModuleKind.ESNext | ModuleKind.CommonJS | undefined; -export interface JSDocOverrideTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocOverrideTag; -} - -export interface JSDocEnumTag extends JSDocTag, Declaration, LocalsContainer { - readonly kind: SyntaxKind.JSDocEnumTag; - readonly parent: JSDoc; - readonly typeExpression: JSDocTypeExpression; -} - -export interface JSDocThisTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocThisTag; - readonly typeExpression: JSDocTypeExpression; -} - -export interface JSDocTemplateTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocTemplateTag; - readonly constraint: JSDocTypeExpression | undefined; - readonly typeParameters: NodeArray; -} - -export interface JSDocSeeTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocSeeTag; - readonly name?: JSDocNameReference; -} - -export interface JSDocReturnTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocReturnTag; - readonly typeExpression?: JSDocTypeExpression; -} - -export interface JSDocTypeTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocTypeTag; - readonly typeExpression: JSDocTypeExpression; -} - -export interface JSDocTypedefTag extends JSDocTag, NamedDeclaration, LocalsContainer { - readonly kind: SyntaxKind.JSDocTypedefTag; - readonly parent: JSDoc; - readonly fullName?: JSDocNamespaceDeclaration | Identifier; - readonly name?: Identifier; - readonly typeExpression?: JSDocTypeExpression | JSDocTypeLiteral; -} - -export interface JSDocCallbackTag extends JSDocTag, NamedDeclaration, LocalsContainer { - readonly kind: SyntaxKind.JSDocCallbackTag; - readonly parent: JSDoc; - readonly fullName?: JSDocNamespaceDeclaration | Identifier; - readonly name?: Identifier; - readonly typeExpression: JSDocSignature; -} - - -export interface JSDocOverloadTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocOverloadTag; - readonly parent: JSDoc; - readonly typeExpression: JSDocSignature; -} - -export interface JSDocThrowsTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocThrowsTag; - readonly typeExpression?: JSDocTypeExpression; -} - -export interface JSDocSignature extends JSDocType, Declaration, JSDocContainer, LocalsContainer { - readonly kind: SyntaxKind.JSDocSignature; - readonly typeParameters?: readonly JSDocTemplateTag[]; - readonly parameters: readonly JSDocParameterTag[]; - readonly type: JSDocReturnTag | undefined; -} - -export interface JSDocPropertyLikeTag extends JSDocTag, Declaration { - readonly parent: JSDoc; - readonly name: EntityName; - readonly typeExpression?: JSDocTypeExpression; - /** Whether the property name came before the type -- non-standard for JSDoc, but Typescript-like */ - readonly isNameFirst: boolean; - readonly isBracketed: boolean; -} - -export interface JSDocPropertyTag extends JSDocPropertyLikeTag { - readonly kind: SyntaxKind.JSDocPropertyTag; -} - -export interface JSDocParameterTag extends JSDocPropertyLikeTag { - readonly kind: SyntaxKind.JSDocParameterTag; -} - -export interface JSDocTypeLiteral extends JSDocType, Declaration { - readonly kind: SyntaxKind.JSDocTypeLiteral; - readonly jsDocPropertyTags?: readonly JSDocPropertyLikeTag[]; - /** If true, then this type literal represents an *array* of its type. */ - readonly isArrayType: boolean; -} - -export interface JSDocSatisfiesTag extends JSDocTag { - readonly kind: SyntaxKind.JSDocSatisfiesTag; - readonly typeExpression: JSDocTypeExpression; -} - -/** @internal */ -export interface JSDocSatisfiesExpression extends ParenthesizedExpression { - readonly _jsDocSatisfiesExpressionBrand: never; -} - -// NOTE: Ensure this is up-to-date with src/debug/debug.ts -export const enum FlowFlags { - Unreachable = 1 << 0, // Unreachable code - Start = 1 << 1, // Start of flow graph - BranchLabel = 1 << 2, // Non-looping junction - LoopLabel = 1 << 3, // Looping junction - Assignment = 1 << 4, // Assignment - TrueCondition = 1 << 5, // Condition known to be true - FalseCondition = 1 << 6, // Condition known to be false - SwitchClause = 1 << 7, // Switch statement clause - ArrayMutation = 1 << 8, // Potential array mutation - Call = 1 << 9, // Potential assertion call - ReduceLabel = 1 << 10, // Temporarily reduce antecedents of label - Referenced = 1 << 11, // Referenced as antecedent once - Shared = 1 << 12, // Referenced as antecedent more than once - - Label = BranchLabel | LoopLabel, - Condition = TrueCondition | FalseCondition, -} - -export type FlowNode = - | FlowStart - | FlowLabel - | FlowAssignment - | FlowCondition - | FlowSwitchClause - | FlowArrayMutation - | FlowCall - | FlowReduceLabel; - -export interface FlowNodeBase { - flags: FlowFlags; - id?: number; // Node id used by flow type cache in checker -} - -// FlowStart represents the start of a control flow. For a function expression or arrow -// function, the node property references the function (which in turn has a flowNode -// property for the containing control flow). -export interface FlowStart extends FlowNodeBase { - node?: FunctionExpression | ArrowFunction | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration; -} - -// FlowLabel represents a junction with multiple possible preceding control flows. -export interface FlowLabel extends FlowNodeBase { - antecedents: FlowNode[] | undefined; -} - -// FlowAssignment represents a node that assigns a value to a narrowable reference, -// i.e. an identifier or a dotted name that starts with an identifier or 'this'. -export interface FlowAssignment extends FlowNodeBase { - node: Expression | VariableDeclaration | BindingElement; - antecedent: FlowNode; -} - -export interface FlowCall extends FlowNodeBase { - node: CallExpression; - antecedent: FlowNode; -} - -// FlowCondition represents a condition that is known to be true or false at the -// node's location in the control flow. -export interface FlowCondition extends FlowNodeBase { - node: Expression; - antecedent: FlowNode; -} - -export interface FlowSwitchClause extends FlowNodeBase { - switchStatement: SwitchStatement; - clauseStart: number; // Start index of case/default clause range - clauseEnd: number; // End index of case/default clause range - antecedent: FlowNode; -} - -// FlowArrayMutation represents a node potentially mutates an array, i.e. an -// operation of the form 'x.push(value)', 'x.unshift(value)' or 'x[n] = value'. -export interface FlowArrayMutation extends FlowNodeBase { - node: CallExpression | BinaryExpression; - antecedent: FlowNode; -} - -export interface FlowReduceLabel extends FlowNodeBase { - target: FlowLabel; - antecedents: FlowNode[]; - antecedent: FlowNode; -} - -export type FlowType = Type | IncompleteType; - -// Incomplete types occur during control flow analysis of loops. An IncompleteType -// is distinguished from a regular type by a flags value of zero. Incomplete type -// objects are internal to the getFlowTypeOfReference function and never escape it. -export interface IncompleteType { - flags: TypeFlags | 0; // No flags set - type: Type; // The type marked incomplete -} - -export interface AmdDependency { - path: string; - name?: string; -} - -/** - * Subset of properties from SourceFile that are used in multiple utility functions - */ -export interface SourceFileLike { - readonly text: string; - /** @internal */ - lineMap?: readonly number[]; - /** @internal */ - getPositionOfLineAndCharacter?(line: number, character: number, allowEdits?: true): number; -} - - -/** @internal */ -export interface RedirectInfo { - /** Source file this redirects to. */ - readonly redirectTarget: SourceFile; - /** - * Source file for the duplicate package. This will not be used by the Program, - * but we need to keep this around so we can watch for changes in underlying. - */ - readonly unredirected: SourceFile; -} - -export type ResolutionMode = ModuleKind.ESNext | ModuleKind.CommonJS | undefined; - -// Source files are declarations when they are external modules. -export interface SourceFile extends Declaration, LocalsContainer { - readonly kind: SyntaxKind.SourceFile; - readonly statements: NodeArray; - readonly endOfFileToken: Token; +// Source files are declarations when they are external modules. +export interface SourceFile extends Declaration, LocalsContainer { + readonly kind: SyntaxKind.SourceFile; + readonly statements: NodeArray; + readonly endOfFileToken: Token; fileName: string; /** @internal */ path: Path; @@ -4733,7 +4494,9 @@ export interface SourceFile extends Declaration, LocalsContainer { /** @internal */ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit; /** @internal */ endFlowNode?: FlowNode; - // TSPLUS EXTENSION START + // + // TSPLUS START + // tsPlusImportAs?: () => string | undefined; tsPlusGlobalImports?: ImportDeclaration[]; tsPlusContext: { @@ -4750,7 +4513,9 @@ export interface SourceFile extends Declaration, LocalsContainer { pipeableIndex: (VariableDeclarationWithIdentifier | FunctionDeclaration)[]; noInherit: (InterfaceDeclaration | TypeAliasDeclaration | ClassDeclaration)[]; } - // TSPLUS EXTENSION END + // + // TSPLUS END + // } /** @internal */ @@ -5255,18 +5020,6 @@ export interface CustomTransformers { afterDeclarations?: (TransformerFactory | CustomTransformerFactory)[]; } -// TSPLUS START -export type ExternalTransformers = - | TransformerFactory - | ExternalCustomTransformers - -export interface ExternalCustomTransformers { - before?: TransformerFactory[] | TransformerFactory; - after?: TransformerFactory[] | TransformerFactory; - afterDeclarations?: TransformerFactory[] | TransformerFactory -} -// TSPLUS END - /** @internal */ export interface EmitTransformers { scriptTransformers: readonly TransformerFactory[]; @@ -5335,7 +5088,7 @@ export interface TypeCheckerHost extends ModuleSpecifierResolutionHost { readonly redirectTargetsMap: RedirectTargetsMap; } -export interface TypeChecker { +export interface TypeChecker extends TsPlusTypeChecker { getTypeOfSymbolAtLocation(symbol: Symbol, node: Node): Type; getTypeOfSymbol(symbol: Symbol): Type; getDeclaredTypeOfSymbol(symbol: Symbol): Type; @@ -5681,42 +5434,7 @@ export interface TypeChecker { /** @internal */ getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement, memberSymbol: Symbol): MemberOverrideStatus; /** @internal */ isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node): boolean; /** @internal */ typeHasCallOrConstructSignatures(type: Type): boolean; - - // TSPLUS START - getExtensions(selfNode: Expression): Map - getFluentExtension(target: Type, name: string): Type | undefined - getGetterExtension(target: Type, name: string): { definition: SourceFile, exportName: string } | undefined - getGetterCompanionExtension(target: Type, name: string): { definition: SourceFile, exportName: string } | undefined - getStaticExtension(target: Type, name: string): TsPlusStaticFunctionExtension | TsPlusStaticValueExtension | undefined - getStaticCompanionExtension(target: Type, name: string): TsPlusStaticFunctionExtension | TsPlusStaticValueExtension | undefined - isPipeCall(node: CallExpression): boolean - getCallExtension(node: Node): TsPlusStaticFunctionExtension | undefined - isTailRec(node: Node): boolean - cloneSymbol(symbol: Symbol): Symbol - getTextOfBinaryOp(kind: SyntaxKind): string | undefined - /* @internal */ getInstantiatedTsPlusSignature(declaration: Declaration, args: Expression[], checkMode: CheckMode | undefined): Signature - getIndexAccessExpressionCache(): Map - isTsPlusMacroCall(node: Node, macro: K): node is TsPlusMacroCallExpression - isTsPlusMacroGetter(node: Node, macro: string): boolean - isCompanionReference(node: Expression): boolean - collectTsPlusFluentTags(statement: Declaration): readonly TsPlusPrioritizedExtensionTag[] - hasExportedPlusTags(statement: Declaration): boolean; - getFluentExtensionForPipeableSymbol(symbol: TsPlusPipeableIdentifierSymbol): TsPlusFluentExtension | undefined - getPrimitiveTypeName(type: Type): string | undefined - getResolvedOperator(node: BinaryExpression): Signature | undefined - getNodeLinks(node: Node): NodeLinks - getTsPlusFiles(): Map> - getTsPlusGlobalImports(): Map - collectTsPlusMacroTags(statement: Declaration): readonly string[] - getTsPlusGlobals(): Symbol[]; - getTsPlusGlobal(name: string): TsPlusGlobalImport | undefined; - findAndCheckDoAncestor(node: Node): void; - getTsPlusExtensionsAtLocation(node: Node): TsPlusExtensionTag[]; - getTsPlusSymbolAtLocation(node: Node): TsPlusSymbol | undefined; - getExtensionsForDeclaration(node: Declaration): TsPlusExtensionTag[] - getSymbolLinks(symbol: Symbol): SymbolLinks - // TSPLUS END -} +} /** @internal */ export const enum MemberOverrideStatus { @@ -6292,13 +6010,17 @@ export interface SymbolLinks { tupleLabelDeclaration?: NamedTupleMember | ParameterDeclaration; // Declaration associated with the tuple's label accessibleChainCache?: Map; filteredIndexSymbolCache?: Map //Symbol with applicable declarations + // // TSPLUS START + // tsPlusTypeAndImplicitTags?: { type: Type, tags: Set } isPossibleCompanionReference?: boolean + // // TSPLUS END + // } /** @internal */ @@ -6482,7 +6204,9 @@ export interface NodeLinks { spreadIndices?: { first: number | undefined, last: number | undefined }; // Indices of first and last spread elements in array literal parameterInitializerContainsUndefined?: boolean; // True if this is a parameter declaration whose type annotation contains "undefined". fakeScopeForSignatureDeclaration?: boolean; // True if this is a fake scope injected into an enclosing declaration chain. - // TSPLUS EXTENSION START + // + // TSPLUS START + // isTsPlusOperatorToken?: boolean; tsPlusCallExtension?: TsPlusStaticFunctionExtension; tsPlusStaticExtension?: TsPlusStaticFunctionExtension; @@ -6511,7 +6235,9 @@ export interface NodeLinks { }; tsPlusLazy?: boolean; tsPlusSymbol?: TsPlusSymbol; - // TSPLUS EXTENSION END + // + // TSPLUS END + // } /** @internal */ @@ -6522,65 +6248,6 @@ export interface SerializedTypeEntry { serializedTypes?: Map; // Collection of types serialized at this location } -// TSPLUS EXTENSION START -export type Derivation = FromBlockScope | FromImplicitScope | FromRule | FromObjectStructure | FromTupleStructure | FromIntersectionStructure | InvalidDerivation | EmptyObjectDerivation | FromPriorDerivation | FromLiteral - -export interface FromBlockScope { - readonly _tag: "FromBlockScope" - readonly type: Type - readonly implicit: NamedDeclaration & { name: Identifier } -} -export interface FromImplicitScope { - readonly _tag: "FromImplicitScope" - readonly type: Type - readonly implicit: Declaration -} -export interface FromPriorDerivation { - readonly _tag: "FromPriorDerivation" - readonly type: Type - readonly derivation: Derivation -} -export interface FromRule { - readonly _tag: "FromRule" - readonly type: Type - readonly rule: Declaration - readonly arguments: Derivation[] - readonly usedBy: FromPriorDerivation[] - readonly lazyRule: Declaration | undefined -} -export interface FromObjectStructure { - readonly _tag: "FromObjectStructure" - readonly type: Type - readonly fields: { - prop: Symbol - value: Derivation - }[] -} -export interface FromTupleStructure { - readonly _tag: "FromTupleStructure" - readonly type: Type - readonly fields: Derivation[] -} -export interface FromIntersectionStructure { - readonly _tag: "FromIntersectionStructure" - readonly type: Type - readonly fields: Derivation[] -} -export interface InvalidDerivation { - readonly _tag: "InvalidDerivation" - readonly type: Type -} -export interface EmptyObjectDerivation { - readonly _tag: "EmptyObjectDerivation" - readonly type: Type -} -export interface FromLiteral { - readonly _tag: "FromLiteral" - readonly type: Type - readonly value: string | number -} -// TSPLUS EXTENSION END - export const enum TypeFlags { Any = 1 << 0, Unknown = 1 << 1, @@ -6696,9 +6363,13 @@ export interface Type { immediateBaseConstraint?: Type; // Immediate base constraint cache /** @internal */ widened?: Type; // Cached widened form of the type - // TSPLUS BEGIN + // + // TSPLUS START + // tsPlusUnified?: boolean + // // TSPLUS END + // } /** @internal */ @@ -9572,10 +9243,14 @@ export interface NodeFactory { cloneNode(node: T): T; /** @internal */ updateModifiers(node: T, modifiers: readonly Modifier[] | ModifierFlags | undefined): T; - // TSPLUS EXTENSION START + // + // TSPLUS START + // /** Create a unique name based on the supplied text. */ createTsPlusUniqueName(text: string, flags?: GeneratedIdentifierFlags): TsPlusUniqueIdentifier; - // TSPLUS EXTENSION END + // + // TSPLUS END + // } /** @internal */ @@ -10510,3 +10185,390 @@ export interface Queue { dequeue(): T; isEmpty(): boolean; } + +// +// TSPLUS START +// +export interface TsPlusTypeChecker { + getExtensions(selfNode: Expression): Map + getFluentExtension(target: Type, name: string): Type | undefined + getGetterExtension(target: Type, name: string): { definition: SourceFile, exportName: string } | undefined + getGetterCompanionExtension(target: Type, name: string): { definition: SourceFile, exportName: string } | undefined + getStaticExtension(target: Type, name: string): TsPlusStaticFunctionExtension | TsPlusStaticValueExtension | undefined + getStaticCompanionExtension(target: Type, name: string): TsPlusStaticFunctionExtension | TsPlusStaticValueExtension | undefined + isPipeCall(node: CallExpression): boolean + getCallExtension(node: Node): TsPlusStaticFunctionExtension | undefined + isTailRec(node: Node): boolean + cloneSymbol(symbol: Symbol): Symbol + getTextOfBinaryOp(kind: SyntaxKind): string | undefined + /* @internal */ getInstantiatedTsPlusSignature(declaration: Declaration, args: Expression[], checkMode: CheckMode | undefined): Signature + getIndexAccessExpressionCache(): Map + isTsPlusMacroCall(node: Node, macro: K): node is TsPlusMacroCallExpression + isTsPlusMacroGetter(node: Node, macro: string): boolean + isCompanionReference(node: Expression): boolean + collectTsPlusFluentTags(statement: Declaration): readonly TsPlusPrioritizedExtensionTag[] + hasExportedPlusTags(statement: Declaration): boolean; + getFluentExtensionForPipeableSymbol(symbol: TsPlusPipeableIdentifierSymbol): TsPlusFluentExtension | undefined + getPrimitiveTypeName(type: Type): string | undefined + getResolvedOperator(node: BinaryExpression): Signature | undefined + getNodeLinks(node: Node): NodeLinks + getTsPlusFiles(): Map> + getTsPlusGlobalImports(): Map + collectTsPlusMacroTags(statement: Declaration): readonly string[] + getTsPlusGlobals(): Symbol[]; + getTsPlusGlobal(name: string): TsPlusGlobalImport | undefined; + findAndCheckDoAncestor(node: Node): void; + getTsPlusExtensionsAtLocation(node: Node, includeDeclaration?: boolean): TsPlusExtensionTag[]; + getTsPlusSymbolAtLocation(node: Node): TsPlusSymbol | undefined; + getExtensionsForDeclaration(node: Declaration): TsPlusExtensionTag[] + getSymbolLinks(symbol: Symbol): SymbolLinks +} + +export type ExternalTransformers = + | TransformerFactory + | ExternalCustomTransformers + +export interface ExternalCustomTransformers { + before?: TransformerFactory[] | TransformerFactory; + after?: TransformerFactory[] | TransformerFactory; + afterDeclarations?: TransformerFactory[] | TransformerFactory +} + +export type Derivation = FromBlockScope | FromImplicitScope | FromRule | FromObjectStructure | FromTupleStructure | FromIntersectionStructure | InvalidDerivation | EmptyObjectDerivation | FromPriorDerivation | FromLiteral + +export interface FromBlockScope { + readonly _tag: "FromBlockScope" + readonly type: Type + readonly implicit: NamedDeclaration & { name: Identifier } +} +export interface FromImplicitScope { + readonly _tag: "FromImplicitScope" + readonly type: Type + readonly implicit: Declaration +} +export interface FromPriorDerivation { + readonly _tag: "FromPriorDerivation" + readonly type: Type + readonly derivation: Derivation +} +export interface FromRule { + readonly _tag: "FromRule" + readonly type: Type + readonly rule: Declaration + readonly arguments: Derivation[] + readonly usedBy: FromPriorDerivation[] + readonly lazyRule: Declaration | undefined +} +export interface FromObjectStructure { + readonly _tag: "FromObjectStructure" + readonly type: Type + readonly fields: { + prop: Symbol + value: Derivation + }[] +} +export interface FromTupleStructure { + readonly _tag: "FromTupleStructure" + readonly type: Type + readonly fields: Derivation[] +} +export interface FromIntersectionStructure { + readonly _tag: "FromIntersectionStructure" + readonly type: Type + readonly fields: Derivation[] +} +export interface InvalidDerivation { + readonly _tag: "InvalidDerivation" + readonly type: Type +} +export interface EmptyObjectDerivation { + readonly _tag: "EmptyObjectDerivation" + readonly type: Type +} +export interface FromLiteral { + readonly _tag: "FromLiteral" + readonly type: Type + readonly value: string | number +} + +export interface TsPlusJSDocDeriveTag extends JSDocTag { + readonly parent: JSDoc | JSDocTypeLiteral; + readonly tagName: Identifier; + readonly comment: `derive ${string}` +} + +export interface TsPlusJSDocImplicitTag extends JSDocTag { + readonly parent: JSDoc | JSDocTypeLiteral; + readonly tagName: Identifier; + readonly comment: `implicit` +} + +export interface TsPlusJSDocTypeTag extends JSDocTag { + readonly parent: JSDoc | JSDocTypeLiteral; + readonly tagName: Identifier; + readonly comment: `type ${string}` +} + +export interface TsPlusJSDocUnifyTag extends JSDocTag { + readonly parent: JSDoc | JSDocTypeLiteral; + readonly tagName: Identifier; + readonly comment: `unify ${string}` +} + +export interface TsPlusJSDocIndexTag extends JSDocTag { + readonly parent: JSDoc | JSDocTypeLiteral; + readonly tagName: Identifier; + readonly comment: `index ${string}` +} + +export interface TsPlusJSDocFluentTag extends JSDocTag { + readonly parent: JSDoc | JSDocTypeLiteral; + readonly tagName: Identifier; + readonly comment: `fluent ${string} ${string}` +} + +export interface TsPlusJSDocGlobalTag extends JSDocTag { + readonly parent: JSDoc | JSDocTypeLiteral; + readonly tagName: Identifier; + readonly comment: `global` +} + +export interface TsPlusExtensionTag { + readonly tagType: string; + readonly target: string; + readonly name: string; +} + +export interface TsPlusPrioritizedExtensionTag extends TsPlusExtensionTag { + readonly priority: number; +} + +export interface TsPlusJSDocPipeableTag extends JSDocTag { + readonly parent: JSDoc | JSDocTypeLiteral; + readonly tagName: Identifier; + readonly comment: `pipeable ${string} ${string}` +} + +export interface TsPlusJSDocGetterTag extends JSDocTag { + readonly parent: JSDoc | JSDocTypeLiteral; + readonly tagName: Identifier; + readonly comment: `getter ${string} ${string}` +} + +export interface TsPlusJSDocStaticTag extends JSDocTag { + readonly parent: JSDoc | JSDocTypeLiteral; + readonly tagName: Identifier; + readonly comment: `static ${string} ${string}` +} + +export interface TsPlusJSDocOperatorTag extends JSDocTag { + readonly parent: JSDoc | JSDocTypeLiteral; + readonly tagName: Identifier; + readonly comment: `operator ${string} ${string}` +} + +export interface TsPlusJSDocMacroTag extends JSDocTag { + readonly parent: JSDoc | JSDocTypeLiteral; + readonly tagName: Identifier; + readonly comment: `macro ${string}` +} + +export type TsPlusMacroCallExpression = CallExpression & { __tsplus_brand: K }; + +export const enum TsPlusSymbolTag { + Fluent = "TsPlusFluentSymbol", + StaticFunction = "TsPlusStaticFunctionSymbol", + StaticValue = "TsPlusStaticValueSymbol", + UnresolvedStatic = "TsPlusUnresolvedStatic", + Getter = "TsPlusGetterSymbol", + GetterVariable = "TsPlusGetterVariableSymbol", + PipeableMacro = "TsPlusPipeableMacroSymbol", + PipeableIdentifier = "TsPlusPipeableSymbol", + PipeableDeclaration = "TsPlusPipeableDeclarationSymbol" +} + + +export interface TsPlusPipeableDeclarationSymbol extends TransientSymbol { + tsPlusTag: TsPlusSymbolTag.PipeableDeclaration + tsPlusDeclaration: FunctionDeclaration | VariableDeclarationWithFunction | VariableDeclarationWithFunctionType; +} + +export interface TsPlusPipeableIdentifierSymbol extends TransientSymbol { + tsPlusTag: TsPlusSymbolTag.PipeableIdentifier; + tsPlusDeclaration: FunctionDeclaration | VariableDeclarationWithFunction | VariableDeclarationWithFunctionType; + tsPlusTypeName: string; + tsPlusName: string; + getTsPlusDataFirstType(): Type; +} + +export interface TsPlusPipeableMacroSymbol extends TransientSymbol { + tsPlusTag: TsPlusSymbolTag.PipeableMacro; + tsPlusDeclaration: VariableDeclaration; + tsPlusDataFirst: FunctionDeclaration | ArrowFunction | FunctionExpression; + tsPlusSourceFile: SourceFile; + tsPlusExportName: string; +} + +export interface TsPlusFluentSymbol extends TransientSymbol { + tsPlusTag: TsPlusSymbolTag.Fluent; + tsPlusName: string; + tsPlusResolvedSignatures: TsPlusSignature[]; +} + +export type VariableDeclarationWithFunction = Omit & { name: Identifier, initializer: ArrowFunction | FunctionExpression }; + +export type VariableDeclarationWithFunctionType = Omit & { name: Identifier, type: FunctionTypeNode }; + +export type VariableDeclarationWithIdentifier = VariableDeclaration & { name: Identifier }; + +export type ClassDeclarationWithIdentifier = ClassDeclaration & { name: Identifier }; + +export interface TsPlusUnresolvedStaticSymbol extends TransientSymbol { + tsPlusTag: TsPlusSymbolTag.UnresolvedStatic + tsPlusDeclaration: VariableDeclaration + tsPlusName: string +} + +export interface TsPlusStaticFunctionSymbol extends TransientSymbol { + tsPlusTag: TsPlusSymbolTag.StaticFunction; + tsPlusDeclaration: FunctionDeclaration | VariableDeclaration; + tsPlusResolvedSignatures: Signature[]; + tsPlusName: string; +} + +export interface TsPlusStaticValueSymbol extends TransientSymbol { + tsPlusTag: TsPlusSymbolTag.StaticValue; + tsPlusResolvedSignatures: TsPlusSignature[]; + tsPlusName: string; + tsPlusDeclaration: (VariableDeclaration | ClassDeclaration) & { name: Identifier }; +} + +export interface TsPlusGetterSymbol extends TransientSymbol { + tsPlusTag: TsPlusSymbolTag.Getter; + tsPlusSelfType: Type; + tsPlusDeclaration: FunctionDeclaration; + tsPlusName: string; +} + +export interface TsPlusGetterVariableSymbol extends TransientSymbol { + tsPlusTag: TsPlusSymbolTag.GetterVariable; + tsPlusDeclaration: VariableDeclaration & { name: Identifier }; + tsPlusSelfType: Type; + tsPlusName: string; +} + +export type TsPlusSymbol = + | TsPlusFluentSymbol + | TsPlusStaticFunctionSymbol + | TsPlusStaticValueSymbol + | TsPlusGetterSymbol + | TsPlusGetterVariableSymbol + | TsPlusPipeableMacroSymbol + | TsPlusPipeableIdentifierSymbol + | TsPlusPipeableDeclarationSymbol; + +export interface TsPlusFluentExtension { + patched: Symbol; + types: { type: Type, signatures: readonly TsPlusSignature[] }[]; + signatures: readonly TsPlusSignature[]; +} + +export interface TsPlusPipeableExtension { + declaration: FunctionDeclaration | VariableDeclarationWithFunction | VariableDeclarationWithFunctionType; + definition: SourceFile; + exportName: string; + typeName: string; + funcName: string; + getTypeAndSignatures(): [Type, TsPlusSignature[]]; +} + +export interface TsPlusUnresolvedStaticExtension { + symbol: Symbol; + declaration: VariableDeclaration | ClassDeclaration; + definition: SourceFile; + target: string; + name: string; + exportName: string; +} + +export interface TsPlusUnresolvedFluentExtensionDefinition { + declaration: (VariableDeclaration & { name: Identifier }) | FunctionDeclaration; + exportName: string; + definition: SourceFile; + priority: number; +} + +export interface TsPlusUnresolvedPipeableExtensionDefinition { + declaration: (VariableDeclaration & { name: Identifier }) | FunctionDeclaration; + exportName: string; + definition: SourceFile; + priority: number; + getTypeAndSignatures(): [Type, TsPlusSignature[]]; +} + +export interface TsPlusUnresolvedPipeableExtension { + definition: Set; + target: string; + name: string; +} + +export interface TsPlusUnresolvedFluentExtension { + definition: Set; + target: string; + name: string; +} + +export interface TsPlusStaticFunctionExtension { + patched: Symbol; + definition: SourceFile; + exportName: string; + type: Type; +} + +export interface TsPlusStaticValueExtension { + patched: Symbol; + definition: SourceFile; + exportName: string; + type: Type; +} + +export interface TsPlusGetterExtension { + patched: (node: Expression) => TsPlusSymbol | undefined + definition: SourceFile + exportName: string + declaration: FunctionDeclaration | VariableDeclarationWithIdentifier +} + +export interface TsPlusOperatorExtension { + patched: Symbol; + definition: SourceFile; + exportName: string; + priority: number; +} + +export interface TsPlusGlobalImport { + declaration: ImportDeclaration; + importSpecifier: ImportSpecifier; + moduleSpecifier: StringLiteral; +} + +export interface TsPlusType extends Type { + tsPlusSymbol: TsPlusSymbol; +} + +export interface TsPlusSignature extends Signature { + tsPlusTag: "TsPlusSignature"; + tsPlusFile: SourceFile; + tsPlusExportName: string; + tsPlusDeclaration?: Declaration; + tsPlusPipeable?: boolean; + tsPlusOriginal: Signature +} + +export interface TsPlusUniqueIdentifier extends Identifier { + tsPlusUniqueIdentifier: true +} + +// +// TSPLUS END +// diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index fce1306f7f..569dfba492 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -247,7 +247,7 @@ import { getTokenAtPosition, isBinaryOperatorToken, TsPlusExtensionTag, - isTsPlusSymbol, + // isTsPlusSymbol, } from "./_namespaces/ts"; import { createImportTracker, @@ -987,7 +987,7 @@ export namespace Core { for (const extension of tsPlusExtensions) { tsPlusReferences = concatenate( tsPlusReferences, - getReferencedExtensionsForSymbol(tsPlusSymbol, extension, node, sourceFiles, sourceFilesSet, checker, cancellationToken, options) + getReferencedExtensionsForSymbol(tsPlusSymbol, extension, node, sourceFiles, sourceFilesSet, checker, cancellationToken, options, true) ); } } @@ -1035,20 +1035,18 @@ export namespace Core { const references = getReferencedSymbolsForSymbol(symbol, node, sourceFiles, sourceFilesSet, checker, cancellationToken, options); // TSPLUS EXTENSION BEGIN - if (isTsPlusSymbol(symbol)) { - let tsPlusDeclarationReferences: SymbolAndEntries[] = [] - if (symbol.valueDeclaration) { - for (const extension of checker.getExtensionsForDeclaration(symbol.valueDeclaration)) { - tsPlusDeclarationReferences = concatenate( - tsPlusDeclarationReferences, - getReferencedExtensionsForSymbol(symbol, extension, undefined, sourceFiles, sourceFilesSet, checker, cancellationToken, options) - ); - } + let tsPlusDeclarationReferences: SymbolAndEntries[] = [] + if (symbol.valueDeclaration) { + for (const extension of checker.getExtensionsForDeclaration(symbol.valueDeclaration)) { + tsPlusDeclarationReferences = concatenate( + tsPlusDeclarationReferences, + getReferencedExtensionsForSymbol(symbol, extension, undefined, sourceFiles, sourceFilesSet, checker, cancellationToken, options, false) + ); } - return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget, tsPlusReferences, tsPlusDeclarationReferences); } + return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget, tsPlusReferences, tsPlusDeclarationReferences); // TSPLUS EXTENSION END - return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget); + // return mergeReferences(program, moduleReferences, references, moduleReferencesOfExportTarget); } export function getAdjustedNode(node: Node, options: Options) { @@ -1354,7 +1352,7 @@ export namespace Core { return references; } - function getReferencedExtensionsForSymbol(referenceSymbol: Symbol, extension: TsPlusExtensionTag, _node: Node | undefined, sourceFiles: readonly SourceFile[], _sourceFilesSet: ReadonlySet, checker: TypeChecker, cancellationToken: CancellationToken, _options: Options): SymbolAndEntries[] { + function getReferencedExtensionsForSymbol(referenceSymbol: Symbol, extension: TsPlusExtensionTag, _node: Node | undefined, sourceFiles: readonly SourceFile[], _sourceFilesSet: ReadonlySet, checker: TypeChecker, cancellationToken: CancellationToken, _options: Options, includeDeclaration: boolean): SymbolAndEntries[] { const references: SymbolAndEntries[] = [] for (const sourceFile of sourceFiles) { cancellationToken.throwIfCancellationRequested() @@ -1363,7 +1361,7 @@ export namespace Core { cancellationToken.throwIfCancellationRequested() const referenceLocation = getTouchingPropertyName(sourceFile, position); if (!isValidReferencePosition(referenceLocation, extension.name)) continue; - const extensions = checker.getTsPlusExtensionsAtLocation(referenceLocation); + const extensions = checker.getTsPlusExtensionsAtLocation(referenceLocation, includeDeclaration); for (const referencedExtension of extensions) { if (referencedExtension === extension) { references.push({