diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 75be36474222f..2a1d4953c3843 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1971,27 +1971,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function runWithoutResolvedSignatureCaching(node: Node | undefined, fn: () => T): T { node = findAncestor(node, isCallLikeOrFunctionLikeExpression); if (node) { - const cachedResolvedSignatures = []; - const cachedTypes = []; - while (node) { - const nodeLinks = getNodeLinks(node); - cachedResolvedSignatures.push([nodeLinks, nodeLinks.resolvedSignature] as const); - nodeLinks.resolvedSignature = undefined; - if (isFunctionExpressionOrArrowFunction(node)) { - const symbolLinks = getSymbolLinks(getSymbolOfDeclaration(node)); - const type = symbolLinks.type; - cachedTypes.push([symbolLinks, type] as const); - symbolLinks.type = undefined; - } - node = findAncestor(node.parent, isCallLikeOrFunctionLikeExpression); - } + sourceFileWithoutResolvedSignatureCaching = getSourceFileOfNode(node); const result = fn(); - for (const [nodeLinks, resolvedSignature] of cachedResolvedSignatures) { - nodeLinks.resolvedSignature = resolvedSignature; - } - for (const [symbolLinks, type] of cachedTypes) { - symbolLinks.type = type; - } + sourceFileWithoutResolvedSignatureCaching = undefined; + symbolLinksWithoutResolvedSignatureCaching = undefined; + nodeLinksWithoutResolvedSignatureCaching = undefined; return result; } return fn(); @@ -2342,7 +2326,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { var maximumSuggestionCount = 10; var mergedSymbols: Symbol[] = []; var symbolLinks: SymbolLinks[] = []; + var symbolLinksWithoutResolvedSignatureCaching: SymbolLinks[] | undefined; var nodeLinks: NodeLinks[] = []; + var nodeLinksWithoutResolvedSignatureCaching: NodeLinks[] | undefined; + var sourceFileWithoutResolvedSignatureCaching: SourceFile | undefined; var flowLoopCaches: Map[] = []; var flowLoopNodes: FlowNode[] = []; var flowLoopKeys: string[] = []; @@ -2917,12 +2904,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getSymbolLinks(symbol: Symbol): SymbolLinks { if (symbol.flags & SymbolFlags.Transient) return (symbol as TransientSymbol).links; const id = getSymbolId(symbol); + if (sourceFileWithoutResolvedSignatureCaching && symbol.valueDeclaration && (isFunctionExpressionOrArrowFunction(symbol.valueDeclaration) || tryGetRootParameterDeclaration(symbol.valueDeclaration)) && getSourceFileOfNode(symbol.valueDeclaration) === sourceFileWithoutResolvedSignatureCaching) { + symbolLinksWithoutResolvedSignatureCaching ??= []; + return symbolLinksWithoutResolvedSignatureCaching[id] ??= new SymbolLinks(); + } return symbolLinks[id] ??= new SymbolLinks(); } function getNodeLinks(node: Node): NodeLinks { const nodeId = getNodeId(node); - return nodeLinks[nodeId] || (nodeLinks[nodeId] = new (NodeLinks as any)()); + if (sourceFileWithoutResolvedSignatureCaching && (isCallLikeOrFunctionLikeExpression(node) || tryGetRootParameterDeclaration(node)) && getSourceFileOfNode(node) === sourceFileWithoutResolvedSignatureCaching) { + nodeLinksWithoutResolvedSignatureCaching ??= []; + return nodeLinksWithoutResolvedSignatureCaching[nodeId] ??= new (NodeLinks as any)(); + } + return nodeLinks[nodeId] ??= new (NodeLinks as any)(); } function getSymbol(symbols: SymbolTable, name: __String, meaning: SymbolFlags): Symbol | undefined { diff --git a/tests/cases/fourslash/noErrorsAfterCompletionsRequestWithinGenericFunction4.ts b/tests/cases/fourslash/noErrorsAfterCompletionsRequestWithinGenericFunction4.ts new file mode 100644 index 0000000000000..35c1648d35a68 --- /dev/null +++ b/tests/cases/fourslash/noErrorsAfterCompletionsRequestWithinGenericFunction4.ts @@ -0,0 +1,32 @@ +/// + +// @strict: true +// @target: esnext +// @lib: esnext + +//// type ObjectFromEntries = T extends readonly [ +//// infer Key extends string | number | symbol, +//// infer Value, +//// ][] +//// ? { [key in Key]: Value } +//// : never; +//// +//// type KeyValuePairs = { +//// [K in keyof T]: [K, T[K]]; +//// }[keyof T]; +//// +//// declare function mapObjectEntries< +//// const T extends object, +//// const TMapped extends [string | number | symbol, unknown], +//// >( +//// obj: T, +//// mapper: ([a, b]: KeyValuePairs) => TMapped, +//// ): ObjectFromEntries; +//// +//// mapObjectEntries({ a: 1, b: 2 }, ([x, y]) => ["a/*1*/", y]); + +verify.completions({ + marker: "1", + exact: ["a"], +}); +verify.noErrors(); diff --git a/tests/cases/fourslash/noErrorsAfterSignatureHelpRequestWithinGenericFunction1.ts b/tests/cases/fourslash/noErrorsAfterSignatureHelpRequestWithinGenericFunction1.ts new file mode 100644 index 0000000000000..69a5d73050903 --- /dev/null +++ b/tests/cases/fourslash/noErrorsAfterSignatureHelpRequestWithinGenericFunction1.ts @@ -0,0 +1,118 @@ +/// + +// @strict: true +// @target: esnext +// @lib: esnext + +//// export interface Pipeable { +//// pipe(this: A, ab: (_: A) => B): B; +//// } +//// +//// type Covariant = (_: never) => A; +//// +//// interface VarianceStruct { +//// readonly _V: string; +//// readonly _A: Covariant; +//// readonly _E: Covariant; +//// readonly _R: Covariant; +//// } +//// +//// declare const EffectTypeId: unique symbol; +//// +//// interface Variance { +//// readonly [EffectTypeId]: VarianceStruct; +//// } +//// +//// interface Effect +//// extends Variance, +//// Pipeable {} +//// +//// interface Class extends Pipeable { +//// new (): Fields; +//// } +//// +//// interface TaggedErrorClass extends Class { +//// readonly _tag: Tag; +//// } +//// +//// declare const TaggedError: (identifier?: string) => < +//// Tag extends string, +//// Fields +//// >( +//// tag: Tag, +//// fieldsOr: Fields +//// ) => TaggedErrorClass< +//// Tag, +//// { +//// readonly _tag: Tag; +//// } +//// >; +//// +//// declare const log: ( +//// ...message: ReadonlyArray +//// ) => Effect; +//// +//// export const categoriesKey = "@effect/error/categories"; +//// +//// export declare const withCategory: >( +//// ...categories: Categories +//// ) => , Ret, C extends { new (...args: Args): Ret }>( +//// C: C +//// ) => C & { +//// new (...args: Args): Ret & { +//// [categoriesKey]: { [Cat in Categories[number]]: true }; +//// }; +//// }; +//// +//// export type AllKeys = E extends { [categoriesKey]: infer Q } +//// ? keyof Q +//// : never; +//// export type ExtractAll = Cats extends any +//// ? Extract +//// : never; +//// +//// export declare const catchCategory: < +//// E, +//// const Categories extends Array>, +//// A2, +//// E2, +//// R2 +//// >( +//// ...args: [ +//// ...Categories, +//// f: (err: ExtractAll) => Effect +//// ] +//// ) => ( +//// effect: Effect +//// ) => Effect>, R | R2>; +//// +//// class FooError extends TaggedError()("FooError", {}).pipe( +//// withCategory("domain") +//// ) {} +//// +//// class BarError extends TaggedError()("BarError", {}).pipe( +//// withCategory("system", "domain") +//// ) {} +//// +//// class BazError extends TaggedError()("BazError", {}).pipe( +//// withCategory("system") +//// ) {} +//// +//// declare const baz: ( +//// x: number +//// ) => Effect; +//// +//// export const program = baz(1).pipe(catchCategory("domain",/*1*/ (_) => log(_._tag))); + +verify.noErrors(); +goTo.marker("1"); +edit.insert(","); +verify.signatureHelpPresentForTriggerReason({ + kind: "characterTyped", + triggerCharacter: ",", +}); +edit.backspace(1); +verify.signatureHelpPresentForTriggerReason({ + kind: "retrigger", +}); +verify.noErrors();