diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3e3ef48845b0a..0f1dbf66a50e3 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4487,7 +4487,7 @@ namespace ts { enclosingDeclaration, flags: flags || NodeBuilderFlags.None, // If no full tracker is provided, fake up a dummy one with a basic limited-functionality moduleResolverHost - tracker: tracker && tracker.trackSymbol ? tracker : { trackSymbol: noop, moduleResolverHost: flags! & NodeBuilderFlags.DoNotIncludeSymbolChain ? { + tracker: tracker && tracker.trackSymbol ? tracker : { trackSymbol: () => false, moduleResolverHost: flags! & NodeBuilderFlags.DoNotIncludeSymbolChain ? { getCommonSourceDirectory: !!(host as Program).getCommonSourceDirectory ? () => (host as Program).getCommonSourceDirectory() : () => "", getSourceFiles: () => host.getSourceFiles(), getCurrentDirectory: () => host.getCurrentDirectory(), @@ -4500,11 +4500,13 @@ namespace ts { getFileIncludeReasons: () => host.getFileIncludeReasons(), } : undefined }, encounteredError: false, + reportedDiagnostic: false, visitedTypes: undefined, symbolDepth: undefined, inferTypeParameters: undefined, approximateLength: 0 }; + context.tracker = wrapSymbolTrackerToReportForContext(context, context.tracker); const resultingNode = cb(context); if (context.truncating && context.flags & NodeBuilderFlags.NoTruncation) { context.tracker?.reportTruncationError?.(); @@ -4512,6 +4514,36 @@ namespace ts { return context.encounteredError ? undefined : resultingNode; } + function wrapSymbolTrackerToReportForContext(context: NodeBuilderContext, tracker: SymbolTracker): SymbolTracker { + const oldTrackSymbol = tracker.trackSymbol; + return { + ...tracker, + reportCyclicStructureError: wrapReportedDiagnostic(tracker.reportCyclicStructureError), + reportInaccessibleThisError: wrapReportedDiagnostic(tracker.reportInaccessibleThisError), + reportInaccessibleUniqueSymbolError: wrapReportedDiagnostic(tracker.reportInaccessibleUniqueSymbolError), + reportLikelyUnsafeImportRequiredError: wrapReportedDiagnostic(tracker.reportLikelyUnsafeImportRequiredError), + reportNonlocalAugmentation: wrapReportedDiagnostic(tracker.reportNonlocalAugmentation), + reportPrivateInBaseOfClassExpression: wrapReportedDiagnostic(tracker.reportPrivateInBaseOfClassExpression), + trackSymbol: oldTrackSymbol && ((...args) => { + const result = oldTrackSymbol(...args); + if (result) { + context.reportedDiagnostic = true; + } + return result; + }), + }; + + function wrapReportedDiagnostic any>(method: T | undefined): T | undefined { + if (!method) { + return method; + } + return (((...args) => { + context.reportedDiagnostic = true; + return method(...args); + }) as T); + } + } + function checkTruncationLength(context: NodeBuilderContext): boolean { if (context.truncating) return context.truncating; return context.truncating = context.approximateLength > ((context.flags & NodeBuilderFlags.NoTruncation) ? noTruncationMaximumTruncationLength : defaultMaximumTruncationLength); @@ -4838,7 +4870,7 @@ namespace ts { } } - function visitAndTransformType(type: Type, transform: (type: Type) => T) { + function visitAndTransformType(type: Type, transform: (type: Type) => T) { const typeId = type.id; const isConstructorObject = getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & SymbolFlags.Class; const id = getObjectFlags(type) & ObjectFlags.Reference && (type).node ? "N" + getNodeId((type).node!) : @@ -4853,6 +4885,20 @@ namespace ts { context.symbolDepth = new Map(); } + const links = context.enclosingDeclaration && getNodeLinks(context.enclosingDeclaration); + const key = `${getTypeId(type)}|${context.flags}`; + if (links) { + links.serializedTypes ||= new Map(); + } + const cachedResult = links?.serializedTypes?.get(key); + if (cachedResult) { + if (cachedResult.truncating) { + context.truncating = true; + } + context.approximateLength += cachedResult.addedLength; + return deepCloneOrReuseNode(cachedResult) as TypeNode as T; + } + let depth: number | undefined; if (id) { depth = context.symbolDepth!.get(id) || 0; @@ -4862,12 +4908,28 @@ namespace ts { context.symbolDepth!.set(id, depth + 1); } context.visitedTypes.add(typeId); + const startLength = context.approximateLength; const result = transform(type); + const addedLength = context.approximateLength - startLength; + if (!context.reportedDiagnostic && !context.encounteredError) { + if (context.truncating) { + (result as any).truncating = true; + } + (result as any).addedLength = addedLength; + links?.serializedTypes?.set(key, result as TypeNode as TypeNode & {truncating?: boolean, addedLength: number}); + } context.visitedTypes.delete(typeId); if (id) { context.symbolDepth!.set(id, depth!); } return result; + + function deepCloneOrReuseNode(node: Node): Node { + if (!nodeIsSynthesized(node) && getParseTreeNode(node) === node) { + return node; + } + return setTextRange(factory.cloneNode(visitEachChild(node, deepCloneOrReuseNode, nullTransformationContext)), node); + } } function createTypeNodeFromObjectType(type: ObjectType): TypeNode { @@ -5998,6 +6060,7 @@ namespace ts { if (initial.typeParameterSymbolList) { initial.typeParameterSymbolList = new Set(initial.typeParameterSymbolList); } + initial.tracker = wrapSymbolTrackerToReportForContext(initial, initial.tracker); return initial; } @@ -6289,11 +6352,13 @@ namespace ts { } } else if (oldcontext.tracker && oldcontext.tracker.trackSymbol) { - oldcontext.tracker.trackSymbol(sym, decl, meaning); + return oldcontext.tracker.trackSymbol(sym, decl, meaning); } - } - } + return false; + }, + }, }; + context.tracker = wrapSymbolTrackerToReportForContext(context, context.tracker); forEachEntry(symbolTable, (symbol, name) => { const baseName = unescapeLeadingUnderscores(name); void getInternalSymbolName(symbol, baseName); // Called to cache values into `usedSymbolNames` and `remappedSymbolNames` @@ -6513,6 +6578,9 @@ namespace ts { const oldContext = context; context = cloneNodeBuilderContext(context); const result = serializeSymbolWorker(symbol, isPrivate, propertyAsAlias); + if (context.reportedDiagnostic) { + oldcontext.reportedDiagnostic = context.reportedDiagnostic; // hoist diagnostic result into outer context + } context = oldContext; return result; } @@ -7286,7 +7354,7 @@ namespace ts { // a visibility error here (as they're not visible within any scope), but we want to hoist them // into the containing scope anyway, so we want to skip the visibility checks. const oldTrack = context.tracker.trackSymbol; - context.tracker.trackSymbol = noop; + context.tracker.trackSymbol = () => false; if (isExportAssignmentCompatibleSymbolName) { results.push(factory.createExportAssignment( /*decorators*/ undefined, @@ -7742,6 +7810,7 @@ namespace ts { // State encounteredError: boolean; + reportedDiagnostic: boolean; visitedTypes: Set | undefined; symbolDepth: ESMap | undefined; inferTypeParameters: TypeParameter[] | undefined; diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 5a56075706720..9732458c7a94d 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -145,8 +145,10 @@ namespace ts { symbolAccessibilityResult.errorSymbolName, symbolAccessibilityResult.errorModuleName)); } + return true; } } + return false; } function trackExternalModuleSymbolOfImportTypeNode(symbol: Symbol) { @@ -156,9 +158,10 @@ namespace ts { } function trackSymbol(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags) { - if (symbol.flags & SymbolFlags.TypeParameter) return; - handleSymbolAccessibilityError(resolver.isSymbolAccessible(symbol, enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ true)); + if (symbol.flags & SymbolFlags.TypeParameter) return false; + const issuedDiagnostic = handleSymbolAccessibilityError(resolver.isSymbolAccessible(symbol, enclosingDeclaration, meaning, /*shouldComputeAliasesToMakeVisible*/ true)); recordTypeReferenceDirectivesIfNecessary(resolver.getTypeReferenceDirectivesForSymbol(symbol, meaning)); + return issuedDiagnostic; } function reportPrivateInBaseOfClassExpression(propertyName: string) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c129fe5ec2d6b..128ea7aafe43f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4987,6 +4987,7 @@ namespace ts { isExhaustive?: boolean; // Is node an exhaustive switch statement skipDirectInference?: true; // Flag set by the API `getContextualType` call on a node when `Completions` is passed to force the checker to skip making inferences to a node's type declarationRequiresScopeChange?: boolean; // Set by `useOuterVariableScopeInParameter` in checker when downlevel emit would change the name resolution scope inside of a parameter. + serializedTypes?: ESMap; // Collection of types serialized at this location } export const enum TypeFlags { @@ -8110,7 +8111,7 @@ namespace ts { // Called when the symbol writer encounters a symbol to write. Currently only used by the // declaration emitter to help determine if it should patch up the final declaration file // with import statements it previously saw (but chose not to emit). - trackSymbol?(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags): void; + trackSymbol?(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags): boolean; reportInaccessibleThisError?(): void; reportPrivateInBaseOfClassExpression?(propertyName: string): void; reportInaccessibleUniqueSymbolError?(): void; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 9bbdea8a6ac2d..3cd3a8d3e60ad 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -83,7 +83,7 @@ namespace ts { increaseIndent: noop, decreaseIndent: noop, clear: () => str = "", - trackSymbol: noop, + trackSymbol: () => false, reportInaccessibleThisError: noop, reportInaccessibleUniqueSymbolError: noop, reportPrivateInBaseOfClassExpression: noop, @@ -4023,7 +4023,7 @@ namespace ts { reportInaccessibleThisError: noop, reportPrivateInBaseOfClassExpression: noop, reportInaccessibleUniqueSymbolError: noop, - trackSymbol: noop, + trackSymbol: () => false, writeKeyword: write, writeOperator: write, writeParameter: write, diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index c6a982546fa22..6900fc6efeb4c 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -18,7 +18,7 @@ namespace ts.codefix { export function getNoopSymbolTrackerWithResolver(context: TypeConstructionContext): SymbolTracker { return { - trackSymbol: noop, + trackSymbol: () => false, moduleResolverHost: getModuleSpecifierResolverHost(context.program, context.host), }; } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index e5d9b0da77564..e91f962e80eef 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -2093,7 +2093,7 @@ namespace ts { increaseIndent: () => { indent++; }, decreaseIndent: () => { indent--; }, clear: resetWriter, - trackSymbol: noop, + trackSymbol: () => false, reportInaccessibleThisError: noop, reportInaccessibleUniqueSymbolError: noop, reportPrivateInBaseOfClassExpression: noop, @@ -2619,6 +2619,7 @@ namespace ts { const res = checker.typeToTypeNode(type, enclosingScope, NodeBuilderFlags.NoTruncation, { trackSymbol: (symbol, declaration, meaning) => { typeIsAccessible = typeIsAccessible && checker.isSymbolAccessible(symbol, declaration, meaning, /*shouldComputeAliasToMarkVisible*/ false).accessibility === SymbolAccessibility.Accessible; + return !typeIsAccessible; }, reportInaccessibleThisError: notAccessible, reportPrivateInBaseOfClassExpression: notAccessible, diff --git a/tests/baselines/reference/declarationsWithRecursiveInternalTypesProduceUniqueTypeParams.types b/tests/baselines/reference/declarationsWithRecursiveInternalTypesProduceUniqueTypeParams.types index b57ceb0b7a4f9..ac96422ad5a87 100644 --- a/tests/baselines/reference/declarationsWithRecursiveInternalTypesProduceUniqueTypeParams.types +++ b/tests/baselines/reference/declarationsWithRecursiveInternalTypesProduceUniqueTypeParams.types @@ -155,7 +155,7 @@ let p2 = p1.deeper({ two: '2' }) >p1.deeper({ two: '2' }) : { result: { one: string; } & { two: string; }; deeper: (child: U) => { result: { one: string; } & { two: string; } & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U & U & U; deeper: (child: U) => any; }; }; }; }; }; }; }; }; }; }; } >p1.deeper : (child: U) => { result: { one: string; } & U; deeper: (child: U) => { result: { one: string; } & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U & U & U & U & U; deeper: any; }; }; }; }; }; }; }; }; }; }; } >p1 : { result: { one: string; }; deeper: (child: U) => { result: { one: string; } & U; deeper: (child: U) => { result: { one: string; } & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U & U & U & U; deeper: (child: U) => any; }; }; }; }; }; }; }; }; }; }; } ->deeper : (child: U) => { result: { one: string; } & U; deeper: (child: U) => { result: { one: string; } & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U & U & U & U & U; deeper: any; }; }; }; }; }; }; }; }; }; }; } +>deeper : (child: U) => { result: { one: string; } & U; deeper: (child: U) => { result: { one: string; } & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & U & U & U & U & U & U & U & U & U & U; deeper: (child: U) => any; }; }; }; }; }; }; }; }; }; } >{ two: '2' } : { two: string; } >two : string >'2' : "2" @@ -181,7 +181,7 @@ let p3 = p2.deeper({ three: '3' }) >p2.deeper({ three: '3' }) : { result: { one: string; } & { two: string; } & { three: string; }; deeper: (child: U) => { result: { one: string; } & { two: string; } & { three: string; } & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & { three: string; } & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & { three: string; } & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & { three: string; } & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & { three: string; } & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & { three: string; } & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & { three: string; } & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & { three: string; } & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & { three: string; } & U & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & { three: string; } & U & U & U & U & U & U & U & U & U & U; deeper: (child: U) => any; }; }; }; }; }; }; }; }; }; }; } >p2.deeper : (child: U) => { result: { one: string; } & { two: string; } & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U & U & U & U; deeper: any; }; }; }; }; }; }; }; }; }; }; } >p2 : { result: { one: string; } & { two: string; }; deeper: (child: U) => { result: { one: string; } & { two: string; } & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U & U & U; deeper: (child: U) => any; }; }; }; }; }; }; }; }; }; }; } ->deeper : (child: U) => { result: { one: string; } & { two: string; } & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U & U & U & U; deeper: any; }; }; }; }; }; }; }; }; }; }; } +>deeper : (child: U) => { result: { one: string; } & { two: string; } & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U & U; deeper: (child: U) => { result: { one: string; } & { two: string; } & U & U & U & U & U & U & U & U & U & U; deeper: (child: U) => any; }; }; }; }; }; }; }; }; }; } >{ three: '3' } : { three: string; } >three : string >'3' : "3"