diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e2668c2fa68a7..8c9d369b4e5c0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4788,22 +4788,39 @@ namespace ts { return typeParameters; } - // Appends the outer type parameters of a node to a set of type parameters and returns the resulting set. The function - // allocates a new array if the input type parameter set is undefined, but otherwise it modifies the set in-place and - // returns the same array. - function appendOuterTypeParameters(typeParameters: TypeParameter[], node: Node): TypeParameter[] { + // Return the outer type parameters of a node or undefined if the node has no outer type parameters. + function getOuterTypeParameters(node: Node, includeThisTypes?: boolean): TypeParameter[] { while (true) { node = node.parent; if (!node) { - return typeParameters; + return undefined; } - if (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || - node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression || - node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.ArrowFunction) { - const declarations = (node).typeParameters; - if (declarations) { - return appendTypeParameters(appendOuterTypeParameters(typeParameters, node), declarations); - } + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.MethodSignature: + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + case SyntaxKind.JSDocFunctionType: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.JSDocTemplateTag: + case SyntaxKind.MappedType: + const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes); + if (node.kind === SyntaxKind.MappedType) { + return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode((node).typeParameter))); + } + const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(node) || emptyArray); + const thisType = includeThisTypes && + (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.InterfaceDeclaration) && + getDeclaredTypeOfClassOrInterface(getSymbolOfNode(node)).thisType; + return thisType ? append(outerAndOwnTypeParameters, thisType) : outerAndOwnTypeParameters; } } } @@ -4811,7 +4828,7 @@ namespace ts { // The outer type parameters are those defined by enclosing generic classes, methods, or functions. function getOuterTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] { const declaration = symbol.flags & SymbolFlags.Class ? symbol.valueDeclaration : getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration); - return appendOuterTypeParameters(/*typeParameters*/ undefined, declaration); + return getOuterTypeParameters(declaration); } // The local type parameters are the combined set of type parameters from all declarations of the class, @@ -6614,11 +6631,30 @@ namespace ts { } function getErasedSignature(signature: Signature): Signature { - if (!signature.typeParameters) return signature; - if (!signature.erasedSignatureCache) { - signature.erasedSignatureCache = instantiateSignature(signature, createTypeEraser(signature.typeParameters), /*eraseTypeParameters*/ true); - } - return signature.erasedSignatureCache; + return signature.typeParameters ? + signature.erasedSignatureCache || (signature.erasedSignatureCache = createErasedSignature(signature)) : + signature; + } + + function createErasedSignature(signature: Signature) { + // Create an instantiation of the signature where all type arguments are the any type. + return instantiateSignature(signature, createTypeEraser(signature.typeParameters), /*eraseTypeParameters*/ true); + } + + function getCanonicalSignature(signature: Signature): Signature { + return signature.typeParameters ? + signature.canonicalSignatureCache || (signature.canonicalSignatureCache = createCanonicalSignature(signature)) : + signature; + } + + function createCanonicalSignature(signature: Signature) { + // Create an instantiation of the signature where each unconstrained type parameter is replaced with + // its original. When a generic class or interface is instantiated, each generic method in the class or + // interface is instantiated with a fresh set of cloned type parameters (which we need to handle scenarios + // where different generations of the same type parameter are in scope). This leads to a lot of new type + // identities, and potentially a lot of work comparing those identities, so here we create an instantiation + // that uses the original type identities for all unconstrained type parameters. + return getSignatureInstantiation(signature, map(signature.typeParameters, tp => tp.target && !getConstraintOfTypeParameter(tp.target) ? tp.target : tp)); } function getOrCreateTypeFromSignature(signature: Signature): ObjectType { @@ -6799,7 +6835,7 @@ namespace ts { const id = getTypeListId(typeArguments); let instantiation = links.instantiations.get(id); if (!instantiation) { - links.instantiations.set(id, instantiation = instantiateTypeNoAlias(type, createTypeMapper(typeParameters, fillMissingTypeArguments(typeArguments, typeParameters, getMinTypeArgumentCount(typeParameters))))); + links.instantiations.set(id, instantiation = instantiateType(type, createTypeMapper(typeParameters, fillMissingTypeArguments(typeArguments, typeParameters, getMinTypeArgumentCount(typeParameters))))); } return instantiation; } @@ -8034,11 +8070,6 @@ namespace ts { return instantiateList(signatures, mapper, instantiateSignature); } - function instantiateCached(type: T, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): T { - const instantiations = mapper.instantiations || (mapper.instantiations = []); - return instantiations[type.id] || (instantiations[type.id] = instantiator(type, mapper)); - } - function makeUnaryTypeMapper(source: Type, target: Type) { return (t: Type) => t === source ? target : t; } @@ -8060,11 +8091,9 @@ namespace ts { function createTypeMapper(sources: TypeParameter[], targets: Type[]): TypeMapper { Debug.assert(targets === undefined || sources.length === targets.length); - const mapper: TypeMapper = sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) : + return sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) : sources.length === 2 ? makeBinaryTypeMapper(sources[0], targets ? targets[0] : anyType, sources[1], targets ? targets[1] : anyType) : - makeArrayTypeMapper(sources, targets); - mapper.mappedTypes = sources; - return mapper; + makeArrayTypeMapper(sources, targets); } function createTypeEraser(sources: TypeParameter[]): TypeMapper { @@ -8075,10 +8104,8 @@ namespace ts { * Maps forward-references to later types parameters to the empty object type. * This is used during inference when instantiating type parameter defaults. */ - function createBackreferenceMapper(typeParameters: TypeParameter[], index: number) { - const mapper: TypeMapper = t => indexOf(typeParameters, t) >= index ? emptyObjectType : t; - mapper.mappedTypes = typeParameters; - return mapper; + function createBackreferenceMapper(typeParameters: TypeParameter[], index: number): TypeMapper { + return t => indexOf(typeParameters, t) >= index ? emptyObjectType : t; } function isInferenceContext(mapper: TypeMapper): mapper is InferenceContext { @@ -8096,15 +8123,11 @@ namespace ts { } function combineTypeMappers(mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper { - const mapper: TypeMapper = t => instantiateType(mapper1(t), mapper2); - mapper.mappedTypes = concatenate(mapper1.mappedTypes, mapper2.mappedTypes); - return mapper; + return t => instantiateType(mapper1(t), mapper2); } - function createReplacementMapper(source: Type, target: Type, baseMapper: TypeMapper) { - const mapper: TypeMapper = t => t === source ? target : baseMapper(t); - mapper.mappedTypes = baseMapper.mappedTypes; - return mapper; + function createReplacementMapper(source: Type, target: Type, baseMapper: TypeMapper): TypeMapper { + return t => t === source ? target : baseMapper(t); } function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter { @@ -8181,13 +8204,53 @@ namespace ts { return result; } - function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper): AnonymousType { - const result = createObjectType(ObjectFlags.Anonymous | ObjectFlags.Instantiated, type.symbol); - result.target = type.objectFlags & ObjectFlags.Instantiated ? type.target : type; - result.mapper = type.objectFlags & ObjectFlags.Instantiated ? combineTypeMappers(type.mapper, mapper) : mapper; - result.aliasSymbol = type.aliasSymbol; - result.aliasTypeArguments = instantiateTypes(type.aliasTypeArguments, mapper); - return result; + function getAnonymousTypeInstantiation(type: AnonymousType, mapper: TypeMapper) { + const target = type.objectFlags & ObjectFlags.Instantiated ? type.target : type; + const symbol = target.symbol; + const links = getSymbolLinks(symbol); + let typeParameters = links.typeParameters; + if (!typeParameters) { + // The first time an anonymous type is instantiated we compute and store a list of the type + // parameters that are in scope (and therefore potentially referenced). For type literals that + // aren't the right hand side of a generic type alias declaration we optimize by reducing the + // set of type parameters to those that are actually referenced somewhere in the literal. + const declaration = symbol.declarations[0]; + const outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true) || emptyArray; + typeParameters = symbol.flags & SymbolFlags.TypeLiteral && !target.aliasTypeArguments ? + filter(outerTypeParameters, tp => isTypeParameterReferencedWithin(tp, declaration)) : + outerTypeParameters; + links.typeParameters = typeParameters; + if (typeParameters.length) { + links.instantiations = createMap(); + links.instantiations.set(getTypeListId(typeParameters), target); + } + } + if (typeParameters.length) { + // We are instantiating an anonymous type that has one or more type parameters in scope. Apply the + // mapper to the type parameters to produce the effective list of type arguments, and compute the + // instantiation cache key from the type IDs of the type arguments. + const combinedMapper = type.objectFlags & ObjectFlags.Instantiated ? combineTypeMappers(type.mapper, mapper) : mapper; + const typeArguments = map(typeParameters, combinedMapper); + const id = getTypeListId(typeArguments); + let result = links.instantiations.get(id); + if (!result) { + const newMapper = createTypeMapper(typeParameters, typeArguments); + result = target.objectFlags & ObjectFlags.Mapped ? instantiateMappedType(target, newMapper) : instantiateAnonymousType(target, newMapper); + links.instantiations.set(id, result); + } + return result; + } + return type; + } + + function isTypeParameterReferencedWithin(tp: TypeParameter, node: Node) { + return tp.isThisType ? forEachChild(node, checkThis) : forEachChild(node, checkIdentifier); + function checkThis(node: Node): boolean { + return node.kind === SyntaxKind.ThisType || forEachChild(node, checkThis); + } + function checkIdentifier(node: Node): boolean { + return node.kind === SyntaxKind.Identifier && isPartOfTypeNode(node) && getTypeFromTypeNode(node) === tp || forEachChild(node, checkIdentifier); + } } function instantiateMappedType(type: MappedType, mapper: TypeMapper): Type { @@ -8204,165 +8267,65 @@ namespace ts { if (typeVariable !== mappedTypeVariable) { return mapType(mappedTypeVariable, t => { if (isMappableType(t)) { - return instantiateMappedObjectType(type, createReplacementMapper(typeVariable, t, mapper)); + return instantiateAnonymousType(type, createReplacementMapper(typeVariable, t, mapper)); } return t; }); } } } - return instantiateMappedObjectType(type, mapper); + return instantiateAnonymousType(type, mapper); } function isMappableType(type: Type) { return type.flags & (TypeFlags.TypeParameter | TypeFlags.Object | TypeFlags.Intersection | TypeFlags.IndexedAccess); } - function instantiateMappedObjectType(type: MappedType, mapper: TypeMapper): Type { - const result = createObjectType(ObjectFlags.Mapped | ObjectFlags.Instantiated, type.symbol); - result.declaration = type.declaration; - result.mapper = type.mapper ? combineTypeMappers(type.mapper, mapper) : mapper; + function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper): AnonymousType { + const result = createObjectType(type.objectFlags | ObjectFlags.Instantiated, type.symbol); + if (type.objectFlags & ObjectFlags.Mapped) { + (result).declaration = (type).declaration; + } + result.target = type; + result.mapper = mapper; result.aliasSymbol = type.aliasSymbol; result.aliasTypeArguments = instantiateTypes(type.aliasTypeArguments, mapper); return result; } - function isSymbolInScopeOfMappedTypeParameter(symbol: Symbol, mapper: TypeMapper) { - if (!(symbol.declarations && symbol.declarations.length)) { - return false; - } - const mappedTypes = mapper.mappedTypes; - // Starting with the parent of the symbol's declaration, check if the mapper maps any of - // the type parameters introduced by enclosing declarations. We just pick the first - // declaration since multiple declarations will all have the same parent anyway. - return !!findAncestor(symbol.declarations[0], node => { - if (node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.SourceFile) { - return "quit"; - } - switch (node.kind) { - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.Constructor: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - const typeParameters = getEffectiveTypeParameterDeclarations(node as DeclarationWithTypeParameters); - if (typeParameters) { - for (const d of typeParameters) { - if (contains(mappedTypes, getDeclaredTypeOfTypeParameter(getSymbolOfNode(d)))) { - return true; - } - } - } - if (isClassLike(node) || node.kind === SyntaxKind.InterfaceDeclaration) { - const thisType = getDeclaredTypeOfClassOrInterface(getSymbolOfNode(node)).thisType; - if (thisType && contains(mappedTypes, thisType)) { - return true; - } - } - break; - case SyntaxKind.MappedType: - if (contains(mappedTypes, getDeclaredTypeOfTypeParameter(getSymbolOfNode((node).typeParameter)))) { - return true; - } - break; - case SyntaxKind.JSDocFunctionType: - const func = node as JSDocFunctionType; - for (const p of func.parameters) { - if (contains(mappedTypes, getTypeOfNode(p))) { - return true; - } - } - break; - } - }); - } - - function isTopLevelTypeAlias(symbol: Symbol) { - if (symbol.declarations && symbol.declarations.length) { - const parentKind = symbol.declarations[0].parent.kind; - return parentKind === SyntaxKind.SourceFile || parentKind === SyntaxKind.ModuleBlock; - } - return false; - } - function instantiateType(type: Type, mapper: TypeMapper): Type { if (type && mapper !== identityMapper) { - // If we are instantiating a type that has a top-level type alias, obtain the instantiation through - // the type alias instead in order to share instantiations for the same type arguments. This can - // dramatically reduce the number of structurally identical types we generate. Note that we can only - // perform this optimization for top-level type aliases. Consider: - // - // function f1(x: T) { - // type Foo = { x: X, t: T }; - // let obj: Foo = { x: x }; - // return obj; - // } - // function f2(x: U) { return f1(x); } - // let z = f2(42); - // - // Above, the declaration of f2 has an inferred return type that is an instantiation of f1's Foo - // equivalent to { x: U, t: U }. When instantiating this return type, we can't go back to Foo's - // cache because all cached instantiations are of the form { x: ???, t: T }, i.e. they have not been - // instantiated for T. Instead, we need to further instantiate the { x: U, t: U } form. - if (type.aliasSymbol && isTopLevelTypeAlias(type.aliasSymbol)) { - if (type.aliasTypeArguments) { - return getTypeAliasInstantiation(type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)); + if (type.flags & TypeFlags.TypeParameter) { + return mapper(type); + } + if (type.flags & TypeFlags.Object) { + if ((type).objectFlags & ObjectFlags.Anonymous) { + // If the anonymous type originates in a declaration of a function, method, class, or + // interface, in an object type literal, or in an object literal expression, we may need + // to instantiate the type because it might reference a type parameter. + return type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations ? + getAnonymousTypeInstantiation(type, mapper) : type; + } + if ((type).objectFlags & ObjectFlags.Mapped) { + return getAnonymousTypeInstantiation(type, mapper); + } + if ((type).objectFlags & ObjectFlags.Reference) { + return createTypeReference((type).target, instantiateTypes((type).typeArguments, mapper)); } - return type; } - return instantiateTypeNoAlias(type, mapper); - } - return type; - } - - function instantiateTypeNoAlias(type: Type, mapper: TypeMapper): Type { - if (type.flags & TypeFlags.TypeParameter) { - return mapper(type); - } - if (type.flags & TypeFlags.Object) { - if ((type).objectFlags & ObjectFlags.Anonymous) { - // If the anonymous type originates in a declaration of a function, method, class, or - // interface, in an object type literal, or in an object literal expression, we may need - // to instantiate the type because it might reference a type parameter. We skip instantiation - // if none of the type parameters that are in scope in the type's declaration are mapped by - // the given mapper, however we can only do that analysis if the type isn't itself an - // instantiation. - return type.symbol && - type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && - ((type).objectFlags & ObjectFlags.Instantiated || isSymbolInScopeOfMappedTypeParameter(type.symbol, mapper)) ? - instantiateCached(type, mapper, instantiateAnonymousType) : type; + if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Primitive)) { + return getUnionType(instantiateTypes((type).types, mapper), /*subtypeReduction*/ false, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)); + } + if (type.flags & TypeFlags.Intersection) { + return getIntersectionType(instantiateTypes((type).types, mapper), type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)); } - if ((type).objectFlags & ObjectFlags.Mapped) { - return instantiateCached(type, mapper, instantiateMappedType); + if (type.flags & TypeFlags.Index) { + return getIndexType(instantiateType((type).type, mapper)); } - if ((type).objectFlags & ObjectFlags.Reference) { - return createTypeReference((type).target, instantiateTypes((type).typeArguments, mapper)); + if (type.flags & TypeFlags.IndexedAccess) { + return getIndexedAccessType(instantiateType((type).objectType, mapper), instantiateType((type).indexType, mapper)); } } - if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Primitive)) { - return getUnionType(instantiateTypes((type).types, mapper), /*subtypeReduction*/ false, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)); - } - if (type.flags & TypeFlags.Intersection) { - return getIntersectionType(instantiateTypes((type).types, mapper), type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)); - } - if (type.flags & TypeFlags.Index) { - return getIndexType(instantiateType((type).type, mapper)); - } - if (type.flags & TypeFlags.IndexedAccess) { - return getIndexedAccessType(instantiateType((type).objectType, mapper), instantiateType((type).indexType, mapper)); - } return type; } @@ -8538,7 +8501,8 @@ namespace ts { return Ternary.False; } - if (source.typeParameters) { + if (source.typeParameters && source.typeParameters !== target.typeParameters) { + target = getCanonicalSignature(target); source = instantiateSignatureInContextOf(source, target, /*contextualMapper*/ undefined, compareTypes); } @@ -10356,7 +10320,6 @@ namespace ts { function createInferenceContext(signature: Signature, flags: InferenceFlags, compareTypes?: TypeComparer, baseInferences?: InferenceInfo[]): InferenceContext { const inferences = baseInferences ? map(baseInferences, cloneInferenceInfo) : map(signature.typeParameters, createInferenceInfo); const context = mapper as InferenceContext; - context.mappedTypes = signature.typeParameters; context.signature = signature; context.inferences = inferences; context.flags = flags; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index c47f8cd2ba451..b905c3e609f60 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -438,8 +438,10 @@ namespace ts { visitNode(cbNode, (node).typeExpression); } case SyntaxKind.JSDocTypeLiteral: - for (const tag of (node as JSDocTypeLiteral).jsDocPropertyTags) { - visitNode(cbNode, tag); + if ((node as JSDocTypeLiteral).jsDocPropertyTags) { + for (const tag of (node as JSDocTypeLiteral).jsDocPropertyTags) { + visitNode(cbNode, tag); + } } return; case SyntaxKind.PartiallyEmittedExpression: @@ -6667,19 +6669,18 @@ namespace ts { if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) { let child: JSDocTypeTag | JSDocPropertyTag | false; let jsdocTypeLiteral: JSDocTypeLiteral; - let alreadyHasTypeTag = false; + let childTypeTag: JSDocTypeTag; const start = scanner.getStartPos(); while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.Property))) { if (!jsdocTypeLiteral) { jsdocTypeLiteral = createNode(SyntaxKind.JSDocTypeLiteral, start); } if (child.kind === SyntaxKind.JSDocTypeTag) { - if (alreadyHasTypeTag) { + if (childTypeTag) { break; } else { - jsdocTypeLiteral.jsDocTypeTag = child; - alreadyHasTypeTag = true; + childTypeTag = child; } } else { @@ -6693,7 +6694,9 @@ namespace ts { if (typeExpression && typeExpression.type.kind === SyntaxKind.ArrayType) { jsdocTypeLiteral.isArrayType = true; } - typedefTag.typeExpression = finishNode(jsdocTypeLiteral); + typedefTag.typeExpression = childTypeTag && !isObjectOrObjectArrayTypeReference(childTypeTag.typeExpression.type) ? + childTypeTag.typeExpression : + finishNode(jsdocTypeLiteral); } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1deef10a6138b..c50197e2268f3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2150,7 +2150,6 @@ namespace ts { export interface JSDocTypeLiteral extends JSDocType { kind: SyntaxKind.JSDocTypeLiteral; jsDocPropertyTags?: ReadonlyArray; - jsDocTypeTag?: JSDocTypeTag; /** If true, then this type literal represents an *array* of its type. */ isArrayType?: boolean; } @@ -3296,13 +3295,12 @@ namespace ts { } /* @internal */ - export interface MappedType extends ObjectType { + export interface MappedType extends AnonymousType { declaration: MappedTypeNode; typeParameter?: TypeParameter; constraintType?: Type; templateType?: Type; modifiersType?: Type; - mapper?: TypeMapper; // Instantiation mapper } export interface EvolvingArrayType extends ObjectType { @@ -3412,6 +3410,8 @@ namespace ts { /* @internal */ erasedSignatureCache?: Signature; // Erased version of signature (deferred) /* @internal */ + canonicalSignatureCache?: Signature; // Canonical version of signature (deferred) + /* @internal */ isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison /* @internal */ typePredicate?: TypePredicate; @@ -3433,8 +3433,6 @@ namespace ts { /* @internal */ export interface TypeMapper { (t: TypeParameter): Type; - mappedTypes?: TypeParameter[]; // Types mapped by this mapper - instantiations?: Type[]; // Cache of instantiations created using this type mapper. } export const enum InferencePriority { diff --git a/src/server/server.ts b/src/server/server.ts index 0fe37dd8ba0d5..0552d86e7a04b 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -235,24 +235,39 @@ namespace ts.server { return `${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}.${d.getMilliseconds()}`; } + interface QueuedOperation { + operationId: string; + operation: () => void; + } + class NodeTypingsInstaller implements ITypingsInstaller { private installer: NodeChildProcess; private installerPidReported = false; private socket: NodeSocket; private projectService: ProjectService; - private throttledOperations: ThrottledOperations; private eventSender: EventSender; + private activeRequestCount = 0; + private requestQueue: QueuedOperation[] = []; + private requestMap = createMap(); // Maps operation ID to newest requestQueue entry with that ID + + // This number is essentially arbitrary. Processing more than one typings request + // at a time makes sense, but having too many in the pipe results in a hang + // (see https://github.com/nodejs/node/issues/7657). + // It would be preferable to base our limit on the amount of space left in the + // buffer, but we have yet to find a way to retrieve that value. + private static readonly maxActiveRequestCount = 10; + private static readonly requestDelayMillis = 100; + constructor( private readonly telemetryEnabled: boolean, private readonly logger: server.Logger, - host: ServerHost, + private readonly host: ServerHost, eventPort: number, readonly globalTypingsCacheLocation: string, readonly typingSafeListLocation: string, private readonly npmLocation: string | undefined, private newLine: string) { - this.throttledOperations = new ThrottledOperations(host); if (eventPort) { const s = net.connect({ port: eventPort }, () => { this.socket = s; @@ -333,12 +348,26 @@ namespace ts.server { this.logger.info(`Scheduling throttled operation: ${JSON.stringify(request)}`); } } - this.throttledOperations.schedule(project.getProjectName(), /*ms*/ 250, () => { + + const operationId = project.getProjectName(); + const operation = () => { if (this.logger.hasLevel(LogLevel.verbose)) { this.logger.info(`Sending request: ${JSON.stringify(request)}`); } this.installer.send(request); - }); + }; + const queuedRequest: QueuedOperation = { operationId, operation }; + + if (this.activeRequestCount < NodeTypingsInstaller.maxActiveRequestCount) { + this.scheduleRequest(queuedRequest); + } + else { + if (this.logger.hasLevel(LogLevel.verbose)) { + this.logger.info(`Deferring request for: ${operationId}`); + } + this.requestQueue.push(queuedRequest); + this.requestMap.set(operationId, queuedRequest); + } } private handleMessage(response: SetTypings | InvalidateCachedTypings | BeginInstallTypes | EndInstallTypes | InitializationFailedResponse) { @@ -399,11 +428,39 @@ namespace ts.server { return; } + if (this.activeRequestCount > 0) { + this.activeRequestCount--; + } + else { + Debug.fail("Received too many responses"); + } + + while (this.requestQueue.length > 0) { + const queuedRequest = this.requestQueue.shift(); + if (this.requestMap.get(queuedRequest.operationId) === queuedRequest) { + this.requestMap.delete(queuedRequest.operationId); + this.scheduleRequest(queuedRequest); + break; + } + + if (this.logger.hasLevel(LogLevel.verbose)) { + this.logger.info(`Skipping defunct request for: ${queuedRequest.operationId}`); + } + } + this.projectService.updateTypingsForProject(response); if (response.kind === ActionSet && this.socket) { this.sendEvent(0, "setTypings", response); } } + + private scheduleRequest(request: QueuedOperation) { + if (this.logger.hasLevel(LogLevel.verbose)) { + this.logger.info(`Scheduling request for: ${request.operationId}`); + } + this.activeRequestCount++; + this.host.setTimeout(request.operation, NodeTypingsInstaller.requestDelayMillis); + } } class IOSession extends Session { diff --git a/src/server/utilities.ts b/src/server/utilities.ts index e2e2a67eaf12d..353c5bcec2158 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -179,6 +179,12 @@ namespace ts.server { constructor(private readonly host: ServerHost) { } + /** + * Wait `number` milliseconds and then invoke `cb`. If, while waiting, schedule + * is called again with the same `operationId`, cancel this operation in favor + * of the new one. (Note that the amount of time the canceled operation had been + * waiting does not affect the amount of time that the new operation waits.) + */ public schedule(operationId: string, delay: number, cb: () => void) { const pendingTimeout = this.pendingTimeouts.get(operationId); if (pendingTimeout) { diff --git a/src/services/signatureHelp.ts b/src/services/signatureHelp.ts index 2976b0d28ee97..7e8e748bb172d 100644 --- a/src/services/signatureHelp.ts +++ b/src/services/signatureHelp.ts @@ -372,8 +372,7 @@ namespace ts.SignatureHelp { if (isTypeParameterList) { isVariadic = false; // type parameter lists are not variadic prefixDisplayParts.push(punctuationPart(SyntaxKind.LessThanToken)); - // Use `.mapper` to ensure we get the generic type arguments even if this is an instantiated version of the signature. - const typeParameters = candidateSignature.mapper ? candidateSignature.mapper.mappedTypes : candidateSignature.typeParameters; + const typeParameters = (candidateSignature.target || candidateSignature).typeParameters; signatureHelpParameters = typeParameters && typeParameters.length > 0 ? map(typeParameters, createSignatureHelpParameterForTypeParameter) : emptyArray; suffixDisplayParts.push(punctuationPart(SyntaxKind.GreaterThanToken)); const parameterParts = mapToDisplayParts(writer => diff --git a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json index f0e42ae632553..08d270286b9fb 100644 --- a/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json +++ b/tests/baselines/reference/JSDocParsing/DocComments.parsesCorrectly.typedefTagWithChildrenTags.json @@ -34,38 +34,6 @@ "kind": "JSDocTypeLiteral", "pos": 26, "end": 98, - "jsDocTypeTag": { - "kind": "JSDocTypeTag", - "pos": 28, - "end": 42, - "atToken": { - "kind": "AtToken", - "pos": 28, - "end": 29 - }, - "tagName": { - "kind": "Identifier", - "pos": 29, - "end": 33, - "escapedText": "type" - }, - "typeExpression": { - "kind": "JSDocTypeExpression", - "pos": 34, - "end": 42, - "type": { - "kind": "TypeReference", - "pos": 35, - "end": 41, - "typeName": { - "kind": "Identifier", - "pos": 35, - "end": 41, - "escapedText": "Object" - } - } - } - }, "jsDocPropertyTags": [ { "kind": "JSDocPropertyTag", diff --git a/tests/baselines/reference/functionConstraintSatisfaction2.errors.txt b/tests/baselines/reference/functionConstraintSatisfaction2.errors.txt index f87312634f82f..7558a97511fc6 100644 --- a/tests/baselines/reference/functionConstraintSatisfaction2.errors.txt +++ b/tests/baselines/reference/functionConstraintSatisfaction2.errors.txt @@ -22,8 +22,6 @@ tests/cases/conformance/types/typeParameters/typeArgumentLists/functionConstrain Type 'void' is not assignable to type 'string'. tests/cases/conformance/types/typeParameters/typeArgumentLists/functionConstraintSatisfaction2.ts(38,10): error TS2345: Argument of type 'U' is not assignable to parameter of type '(x: string) => string'. Type 'T' is not assignable to type '(x: string) => string'. - Type '() => void' is not assignable to type '(x: string) => string'. - Type 'void' is not assignable to type 'string'. ==== tests/cases/conformance/types/typeParameters/typeArgumentLists/functionConstraintSatisfaction2.ts (13 errors) ==== @@ -102,7 +100,5 @@ tests/cases/conformance/types/typeParameters/typeArgumentLists/functionConstrain ~ !!! error TS2345: Argument of type 'U' is not assignable to parameter of type '(x: string) => string'. !!! error TS2345: Type 'T' is not assignable to type '(x: string) => string'. -!!! error TS2345: Type '() => void' is not assignable to type '(x: string) => string'. -!!! error TS2345: Type 'void' is not assignable to type 'string'. } \ No newline at end of file diff --git a/tests/baselines/reference/jsdocTwoLineTypedef.js b/tests/baselines/reference/jsdocTwoLineTypedef.js new file mode 100644 index 0000000000000..b48d6a89a2154 --- /dev/null +++ b/tests/baselines/reference/jsdocTwoLineTypedef.js @@ -0,0 +1,10 @@ +//// [jsdocTwoLineTypedef.ts] +// Regression from #18301 +/** + * @typedef LoadCallback + * @type {function} + */ +type LoadCallback = void; + + +//// [jsdocTwoLineTypedef.js] diff --git a/tests/baselines/reference/jsdocTwoLineTypedef.symbols b/tests/baselines/reference/jsdocTwoLineTypedef.symbols new file mode 100644 index 0000000000000..80a69e5f52cff --- /dev/null +++ b/tests/baselines/reference/jsdocTwoLineTypedef.symbols @@ -0,0 +1,9 @@ +=== tests/cases/conformance/jsdoc/jsdocTwoLineTypedef.ts === +// Regression from #18301 +/** + * @typedef LoadCallback + * @type {function} + */ +type LoadCallback = void; +>LoadCallback : Symbol(LoadCallback, Decl(jsdocTwoLineTypedef.ts, 0, 0)) + diff --git a/tests/baselines/reference/jsdocTwoLineTypedef.types b/tests/baselines/reference/jsdocTwoLineTypedef.types new file mode 100644 index 0000000000000..5e0d05b3feb2e --- /dev/null +++ b/tests/baselines/reference/jsdocTwoLineTypedef.types @@ -0,0 +1,9 @@ +=== tests/cases/conformance/jsdoc/jsdocTwoLineTypedef.ts === +// Regression from #18301 +/** + * @typedef LoadCallback + * @type {function} + */ +type LoadCallback = void; +>LoadCallback : void + diff --git a/tests/baselines/reference/limitDeepInstantiations.errors.txt b/tests/baselines/reference/limitDeepInstantiations.errors.txt index 330e5fbc8e43e..70718199d2bdf 100644 --- a/tests/baselines/reference/limitDeepInstantiations.errors.txt +++ b/tests/baselines/reference/limitDeepInstantiations.errors.txt @@ -1,4 +1,4 @@ -tests/cases/compiler/limitDeepInstantiations.ts(3,35): error TS2550: Generic type instantiation is excessively deep and possibly infinite. +tests/cases/compiler/limitDeepInstantiations.ts(3,35): error TS2502: '"true"' is referenced directly or indirectly in its own type annotation. tests/cases/compiler/limitDeepInstantiations.ts(5,13): error TS2344: Type '"false"' does not satisfy the constraint '"true"'. @@ -7,7 +7,7 @@ tests/cases/compiler/limitDeepInstantiations.ts(5,13): error TS2344: Type '"fals type Foo = { "true": Foo> }[T]; ~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS2550: Generic type instantiation is excessively deep and possibly infinite. +!!! error TS2502: '"true"' is referenced directly or indirectly in its own type annotation. let f1: Foo<"true", {}>; let f2: Foo<"false", {}>; ~~~~~~~ diff --git a/tests/baselines/reference/promisePermutations.errors.txt b/tests/baselines/reference/promisePermutations.errors.txt index a876345ffa100..c5527d5aa6745 100644 --- a/tests/baselines/reference/promisePermutations.errors.txt +++ b/tests/baselines/reference/promisePermutations.errors.txt @@ -45,7 +45,6 @@ tests/cases/compiler/promisePermutations.ts(134,19): error TS2345: Argument of t tests/cases/compiler/promisePermutations.ts(137,33): error TS2345: Argument of type '(x: any) => IPromise' is not assignable to parameter of type '(error: any) => IPromise'. Type 'IPromise' is not assignable to type 'IPromise'. tests/cases/compiler/promisePermutations.ts(144,35): error TS2345: Argument of type '(x: any) => IPromise' is not assignable to parameter of type '(error: any) => IPromise'. - Type 'IPromise' is not assignable to type 'IPromise'. tests/cases/compiler/promisePermutations.ts(152,36): error TS2345: Argument of type '(x: any) => IPromise' is not assignable to parameter of type '(error: any) => Promise'. Type 'IPromise' is not assignable to type 'Promise'. Types of property 'then' are incompatible. @@ -290,7 +289,6 @@ tests/cases/compiler/promisePermutations.ts(160,21): error TS2345: Argument of t var r10d = r10.then(testFunction, sIPromise, nIPromise); // ok ~~~~~~~~~ !!! error TS2345: Argument of type '(x: any) => IPromise' is not assignable to parameter of type '(error: any) => IPromise'. -!!! error TS2345: Type 'IPromise' is not assignable to type 'IPromise'. var r10e = r10.then(testFunction, nIPromise, sIPromise).then(sIPromise, sIPromise, sIPromise); // ok var s10 = testFunction10P(x => x); var s10a = s10.then(testFunction10, testFunction10, testFunction10); // ok diff --git a/tests/baselines/reference/promisePermutations2.errors.txt b/tests/baselines/reference/promisePermutations2.errors.txt index 871ee2ae2c347..955063797c0e7 100644 --- a/tests/baselines/reference/promisePermutations2.errors.txt +++ b/tests/baselines/reference/promisePermutations2.errors.txt @@ -45,7 +45,6 @@ tests/cases/compiler/promisePermutations2.ts(133,19): error TS2345: Argument of tests/cases/compiler/promisePermutations2.ts(136,33): error TS2345: Argument of type '(x: any) => IPromise' is not assignable to parameter of type '(error: any) => IPromise'. Type 'IPromise' is not assignable to type 'IPromise'. tests/cases/compiler/promisePermutations2.ts(143,35): error TS2345: Argument of type '(x: any) => IPromise' is not assignable to parameter of type '(error: any) => IPromise'. - Type 'IPromise' is not assignable to type 'IPromise'. tests/cases/compiler/promisePermutations2.ts(151,36): error TS2345: Argument of type '(x: any) => IPromise' is not assignable to parameter of type '(error: any) => Promise'. Type 'IPromise' is not assignable to type 'Promise'. Types of property 'then' are incompatible. @@ -289,7 +288,6 @@ tests/cases/compiler/promisePermutations2.ts(159,21): error TS2345: Argument of var r10d = r10.then(testFunction, sIPromise, nIPromise); // error ~~~~~~~~~ !!! error TS2345: Argument of type '(x: any) => IPromise' is not assignable to parameter of type '(error: any) => IPromise'. -!!! error TS2345: Type 'IPromise' is not assignable to type 'IPromise'. var r10e = r10.then(testFunction, nIPromise, sIPromise).then(sIPromise, sIPromise, sIPromise); // ok var s10 = testFunction10P(x => x); var s10a = s10.then(testFunction10, testFunction10, testFunction10); // ok diff --git a/tests/baselines/reference/promisePermutations3.errors.txt b/tests/baselines/reference/promisePermutations3.errors.txt index 9d09559c5d32e..89a4cffe68813 100644 --- a/tests/baselines/reference/promisePermutations3.errors.txt +++ b/tests/baselines/reference/promisePermutations3.errors.txt @@ -48,7 +48,6 @@ tests/cases/compiler/promisePermutations3.ts(133,19): error TS2345: Argument of tests/cases/compiler/promisePermutations3.ts(136,33): error TS2345: Argument of type '(x: any) => IPromise' is not assignable to parameter of type '(error: any) => IPromise'. Type 'IPromise' is not assignable to type 'IPromise'. tests/cases/compiler/promisePermutations3.ts(143,35): error TS2345: Argument of type '(x: any) => IPromise' is not assignable to parameter of type '(error: any) => IPromise'. - Type 'IPromise' is not assignable to type 'IPromise'. tests/cases/compiler/promisePermutations3.ts(151,36): error TS2345: Argument of type '(x: any) => IPromise' is not assignable to parameter of type '(error: any) => Promise'. Type 'IPromise' is not assignable to type 'Promise'. Types of property 'then' are incompatible. @@ -301,7 +300,6 @@ tests/cases/compiler/promisePermutations3.ts(165,21): error TS2345: Argument of var r10d = r10.then(testFunction, sIPromise, nIPromise); // error ~~~~~~~~~ !!! error TS2345: Argument of type '(x: any) => IPromise' is not assignable to parameter of type '(error: any) => IPromise'. -!!! error TS2345: Type 'IPromise' is not assignable to type 'IPromise'. var r10e = r10.then(testFunction, nIPromise, sIPromise).then(sIPromise, sIPromise, sIPromise); // ok var s10 = testFunction10P(x => x); var s10a = s10.then(testFunction10, testFunction10, testFunction10); // ok diff --git a/tests/cases/conformance/jsdoc/jsdocTwoLineTypedef.ts b/tests/cases/conformance/jsdoc/jsdocTwoLineTypedef.ts new file mode 100644 index 0000000000000..2a7ad0d7dbfca --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTwoLineTypedef.ts @@ -0,0 +1,6 @@ +// Regression from #18301 +/** + * @typedef LoadCallback + * @type {function} + */ +type LoadCallback = void;