diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8a96878d567..186a63471e0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1010,6 +1010,7 @@ import { tokenToString, tracing, TracingNode, + TrackedSymbol, TransientSymbol, TransientSymbolLinks, tryAddToSet, @@ -6424,6 +6425,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { symbolDepth: undefined, inferTypeParameters: undefined, approximateLength: 0, + trackedSymbols: undefined, }; context.tracker = new SymbolTrackerImpl(context, tracker, moduleResolverHost); const resultingNode = cb(context); @@ -6922,6 +6924,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } const cachedResult = links?.serializedTypes?.get(key); if (cachedResult) { + // TODO:: check if we instead store late painted statements associated with this? + cachedResult.trackedSymbols?.forEach( + ([symbol, enclosingDeclaration, meaning]) => + context.tracker.trackSymbol( + symbol, + enclosingDeclaration, + meaning, + ), + ); if (cachedResult.truncating) { context.truncating = true; } @@ -6942,7 +6953,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const result = transform(type); const addedLength = context.approximateLength - startLength; if (!context.reportedDiagnostic && !context.encounteredError) { - links?.serializedTypes?.set(key, { node: result, truncating: context.truncating, addedLength }); + links?.serializedTypes?.set(key, { + node: result, + truncating: context.truncating, + addedLength, + trackedSymbols: context.trackedSymbols, + }); } context.visitedTypes.delete(typeId); if (id) { @@ -8860,6 +8876,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (context.reportedDiagnostic) { oldcontext.reportedDiagnostic = context.reportedDiagnostic; // hoist diagnostic result into outer context } + if (context.trackedSymbols) { + if (!oldContext.trackedSymbols) oldContext.trackedSymbols = context.trackedSymbols; + else Debug.assert(context.trackedSymbols === oldContext.trackedSymbols); + } context = oldContext; } } @@ -50378,6 +50398,7 @@ interface NodeBuilderContext { // State encounteredError: boolean; reportedDiagnostic: boolean; + trackedSymbols: TrackedSymbol[] | undefined; visitedTypes: Set | undefined; symbolDepth: Map | undefined; inferTypeParameters: TypeParameter[] | undefined; @@ -50418,6 +50439,8 @@ class SymbolTrackerImpl implements SymbolTracker { this.onDiagnosticReported(); return true; } + // Skip recording type parameters as they dont contribute to late painted statements + if (!(symbol.flags & SymbolFlags.TypeParameter)) (this.context.trackedSymbols ??= []).push([symbol, enclosingDeclaration, meaning]); } return false; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 50bfc2839ba..bb1505131fa 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6031,11 +6031,14 @@ export interface NodeLinks { assertionExpressionType?: Type; // Cached type of the expression of a type assertion } +/** @internal */ +export type TrackedSymbol = [symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags]; /** @internal */ export interface SerializedTypeEntry { node: TypeNode; truncating?: boolean; addedLength: number; + trackedSymbols: readonly TrackedSymbol[] | undefined; } // dprint-ignore diff --git a/src/testRunner/unittests/tsc/incremental.ts b/src/testRunner/unittests/tsc/incremental.ts index 5eaa5b15644..3ed324afd73 100644 --- a/src/testRunner/unittests/tsc/incremental.ts +++ b/src/testRunner/unittests/tsc/incremental.ts @@ -908,4 +908,55 @@ console.log(a);`, }], baselinePrograms: true, }); + + verifyTsc({ + scenario: "incremental", + subScenario: "generates typerefs correctly", + commandLineArgs: ["-p", `/src/project`], + fs: () => + loadProjectFromFiles({ + "/src/project/tsconfig.json": jsonToReadableText({ + compilerOptions: { + composite: true, + outDir: "outDir", + checkJs: true, + }, + include: ["src"], + }), + "/src/project/src/box.ts": Utils.dedent` + export interface Box { + unbox(): T + } + `, + "/src/project/src/bug.js": Utils.dedent` + import * as B from "./box.js" + import * as W from "./wrap.js" + + /** + * @template {object} C + * @param {C} source + * @returns {W.Wrap} + */ + const wrap = source => { + throw source + } + + /** + * @returns {B.Box} + */ + const box = (n = 0) => ({ unbox: () => n }) + + export const bug = wrap({ n: box(1) }); + `, + "/src/project/src/wrap.ts": Utils.dedent` + export type Wrap = { + [K in keyof C]: { wrapped: C[K] } + } + `, + }), + edits: [{ + caption: "modify js file", + edit: fs => appendText(fs, "/src/project/src/bug.js", `export const something = 1;`), + }], + }); }); diff --git a/tests/baselines/reference/tsc/incremental/generates-typerefs-correctly.js b/tests/baselines/reference/tsc/incremental/generates-typerefs-correctly.js new file mode 100644 index 00000000000..920d2045d9e --- /dev/null +++ b/tests/baselines/reference/tsc/incremental/generates-typerefs-correctly.js @@ -0,0 +1,371 @@ +currentDirectory:: / useCaseSensitiveFileNames: false +Input:: +//// [/lib/lib.d.ts] +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +declare const console: { log(msg: any): void; }; + +//// [/src/project/src/box.ts] +export interface Box { + unbox(): T +} + + +//// [/src/project/src/bug.js] +import * as B from "./box.js" +import * as W from "./wrap.js" + +/** + * @template {object} C + * @param {C} source + * @returns {W.Wrap} + */ +const wrap = source => { +throw source +} + +/** + * @returns {B.Box} + */ +const box = (n = 0) => ({ unbox: () => n }) + +export const bug = wrap({ n: box(1) }); + + +//// [/src/project/src/wrap.ts] +export type Wrap = { + [K in keyof C]: { wrapped: C[K] } +} + + +//// [/src/project/tsconfig.json] +{ + "compilerOptions": { + "composite": true, + "outDir": "outDir", + "checkJs": true + }, + "include": [ + "src" + ] +} + + + +Output:: +/lib/tsc -p /src/project +exitCode:: ExitStatus.Success + + +//// [/src/project/outDir/src/box.d.ts] +export interface Box { + unbox(): T; +} + + +//// [/src/project/outDir/src/box.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); + + +//// [/src/project/outDir/src/bug.d.ts] +export const bug: W.Wrap<{ + n: B.Box; +}>; +import * as B from "./box.js"; +import * as W from "./wrap.js"; + + +//// [/src/project/outDir/src/bug.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.bug = void 0; +var B = require("./box.js"); +var W = require("./wrap.js"); +/** + * @template {object} C + * @param {C} source + * @returns {W.Wrap} + */ +var wrap = function (source) { + throw source; +}; +/** + * @returns {B.Box} + */ +var box = function (n) { + if (n === void 0) { n = 0; } + return ({ unbox: function () { return n; } }); +}; +exports.bug = wrap({ n: box(1) }); + + +//// [/src/project/outDir/src/wrap.d.ts] +export type Wrap = { + [K in keyof C]: { + wrapped: C[K]; + }; +}; + + +//// [/src/project/outDir/src/wrap.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); + + +//// [/src/project/outDir/tsconfig.tsbuildinfo] +{"program":{"fileNames":["../../../lib/lib.d.ts","../src/box.ts","../src/wrap.ts","../src/bug.js"],"fileInfos":[{"version":"3858781397-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true},{"version":"-14267342128-export interface Box {\n unbox(): T\n}\n","signature":"-15554117365-export interface Box {\n unbox(): T;\n}\n"},{"version":"-7208318765-export type Wrap = {\n [K in keyof C]: { wrapped: C[K] }\n}\n","signature":"-7604652776-export type Wrap = {\n [K in keyof C]: {\n wrapped: C[K];\n };\n};\n"},{"version":"-27771690375-import * as B from \"./box.js\"\nimport * as W from \"./wrap.js\"\n\n/**\n * @template {object} C\n * @param {C} source\n * @returns {W.Wrap}\n */\nconst wrap = source => {\nthrow source\n}\n\n/**\n * @returns {B.Box}\n */\nconst box = (n = 0) => ({ unbox: () => n })\n\nexport const bug = wrap({ n: box(1) });\n","signature":"-2569667161-export const bug: W.Wrap<{\n n: B.Box;\n}>;\nimport * as B from \"./box.js\";\nimport * as W from \"./wrap.js\";\n"}],"root":[[2,4]],"options":{"checkJs":true,"composite":true,"outDir":"./"},"fileIdsList":[[2,3]],"referencedMap":[[4,1]],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[1,2,4,3],"latestChangedDtsFile":"./src/bug.d.ts"},"version":"FakeTSVersion"} + +//// [/src/project/outDir/tsconfig.tsbuildinfo.readable.baseline.txt] +{ + "program": { + "fileNames": [ + "../../../lib/lib.d.ts", + "../src/box.ts", + "../src/wrap.ts", + "../src/bug.js" + ], + "fileNamesList": [ + [ + "../src/box.ts", + "../src/wrap.ts" + ] + ], + "fileInfos": { + "../../../lib/lib.d.ts": { + "original": { + "version": "3858781397-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "affectsGlobalScope": true + }, + "version": "3858781397-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "signature": "3858781397-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "affectsGlobalScope": true + }, + "../src/box.ts": { + "original": { + "version": "-14267342128-export interface Box {\n unbox(): T\n}\n", + "signature": "-15554117365-export interface Box {\n unbox(): T;\n}\n" + }, + "version": "-14267342128-export interface Box {\n unbox(): T\n}\n", + "signature": "-15554117365-export interface Box {\n unbox(): T;\n}\n" + }, + "../src/wrap.ts": { + "original": { + "version": "-7208318765-export type Wrap = {\n [K in keyof C]: { wrapped: C[K] }\n}\n", + "signature": "-7604652776-export type Wrap = {\n [K in keyof C]: {\n wrapped: C[K];\n };\n};\n" + }, + "version": "-7208318765-export type Wrap = {\n [K in keyof C]: { wrapped: C[K] }\n}\n", + "signature": "-7604652776-export type Wrap = {\n [K in keyof C]: {\n wrapped: C[K];\n };\n};\n" + }, + "../src/bug.js": { + "original": { + "version": "-27771690375-import * as B from \"./box.js\"\nimport * as W from \"./wrap.js\"\n\n/**\n * @template {object} C\n * @param {C} source\n * @returns {W.Wrap}\n */\nconst wrap = source => {\nthrow source\n}\n\n/**\n * @returns {B.Box}\n */\nconst box = (n = 0) => ({ unbox: () => n })\n\nexport const bug = wrap({ n: box(1) });\n", + "signature": "-2569667161-export const bug: W.Wrap<{\n n: B.Box;\n}>;\nimport * as B from \"./box.js\";\nimport * as W from \"./wrap.js\";\n" + }, + "version": "-27771690375-import * as B from \"./box.js\"\nimport * as W from \"./wrap.js\"\n\n/**\n * @template {object} C\n * @param {C} source\n * @returns {W.Wrap}\n */\nconst wrap = source => {\nthrow source\n}\n\n/**\n * @returns {B.Box}\n */\nconst box = (n = 0) => ({ unbox: () => n })\n\nexport const bug = wrap({ n: box(1) });\n", + "signature": "-2569667161-export const bug: W.Wrap<{\n n: B.Box;\n}>;\nimport * as B from \"./box.js\";\nimport * as W from \"./wrap.js\";\n" + } + }, + "root": [ + [ + [ + 2, + 4 + ], + [ + "../src/box.ts", + "../src/wrap.ts", + "../src/bug.js" + ] + ] + ], + "options": { + "checkJs": true, + "composite": true, + "outDir": "./" + }, + "referencedMap": { + "../src/bug.js": [ + "../src/box.ts", + "../src/wrap.ts" + ] + }, + "exportedModulesMap": {}, + "semanticDiagnosticsPerFile": [ + "../../../lib/lib.d.ts", + "../src/box.ts", + "../src/bug.js", + "../src/wrap.ts" + ], + "latestChangedDtsFile": "./src/bug.d.ts" + }, + "version": "FakeTSVersion", + "size": 1698 +} + + + +Change:: modify js file +Input:: +//// [/src/project/src/bug.js] +import * as B from "./box.js" +import * as W from "./wrap.js" + +/** + * @template {object} C + * @param {C} source + * @returns {W.Wrap} + */ +const wrap = source => { +throw source +} + +/** + * @returns {B.Box} + */ +const box = (n = 0) => ({ unbox: () => n }) + +export const bug = wrap({ n: box(1) }); +export const something = 1; + + + +Output:: +/lib/tsc -p /src/project +exitCode:: ExitStatus.Success + + +//// [/src/project/outDir/src/bug.d.ts] +export const bug: W.Wrap<{ + n: B.Box; +}>; +export const something: 1; +import * as B from "./box.js"; +import * as W from "./wrap.js"; + + +//// [/src/project/outDir/src/bug.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.something = exports.bug = void 0; +var B = require("./box.js"); +var W = require("./wrap.js"); +/** + * @template {object} C + * @param {C} source + * @returns {W.Wrap} + */ +var wrap = function (source) { + throw source; +}; +/** + * @returns {B.Box} + */ +var box = function (n) { + if (n === void 0) { n = 0; } + return ({ unbox: function () { return n; } }); +}; +exports.bug = wrap({ n: box(1) }); +exports.something = 1; + + +//// [/src/project/outDir/tsconfig.tsbuildinfo] +{"program":{"fileNames":["../../../lib/lib.d.ts","../src/box.ts","../src/wrap.ts","../src/bug.js"],"fileInfos":[{"version":"3858781397-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true},{"version":"-14267342128-export interface Box {\n unbox(): T\n}\n","signature":"-15554117365-export interface Box {\n unbox(): T;\n}\n"},{"version":"-7208318765-export type Wrap = {\n [K in keyof C]: { wrapped: C[K] }\n}\n","signature":"-7604652776-export type Wrap = {\n [K in keyof C]: {\n wrapped: C[K];\n };\n};\n"},{"version":"-25729561895-import * as B from \"./box.js\"\nimport * as W from \"./wrap.js\"\n\n/**\n * @template {object} C\n * @param {C} source\n * @returns {W.Wrap}\n */\nconst wrap = source => {\nthrow source\n}\n\n/**\n * @returns {B.Box}\n */\nconst box = (n = 0) => ({ unbox: () => n })\n\nexport const bug = wrap({ n: box(1) });\nexport const something = 1;","signature":"-7681488146-export const bug: W.Wrap<{\n n: B.Box;\n}>;\nexport const something: 1;\nimport * as B from \"./box.js\";\nimport * as W from \"./wrap.js\";\n"}],"root":[[2,4]],"options":{"checkJs":true,"composite":true,"outDir":"./"},"fileIdsList":[[2,3]],"referencedMap":[[4,1]],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[1,2,4,3],"latestChangedDtsFile":"./src/bug.d.ts"},"version":"FakeTSVersion"} + +//// [/src/project/outDir/tsconfig.tsbuildinfo.readable.baseline.txt] +{ + "program": { + "fileNames": [ + "../../../lib/lib.d.ts", + "../src/box.ts", + "../src/wrap.ts", + "../src/bug.js" + ], + "fileNamesList": [ + [ + "../src/box.ts", + "../src/wrap.ts" + ] + ], + "fileInfos": { + "../../../lib/lib.d.ts": { + "original": { + "version": "3858781397-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "affectsGlobalScope": true + }, + "version": "3858781397-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "signature": "3858781397-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ndeclare const console: { log(msg: any): void; };", + "affectsGlobalScope": true + }, + "../src/box.ts": { + "original": { + "version": "-14267342128-export interface Box {\n unbox(): T\n}\n", + "signature": "-15554117365-export interface Box {\n unbox(): T;\n}\n" + }, + "version": "-14267342128-export interface Box {\n unbox(): T\n}\n", + "signature": "-15554117365-export interface Box {\n unbox(): T;\n}\n" + }, + "../src/wrap.ts": { + "original": { + "version": "-7208318765-export type Wrap = {\n [K in keyof C]: { wrapped: C[K] }\n}\n", + "signature": "-7604652776-export type Wrap = {\n [K in keyof C]: {\n wrapped: C[K];\n };\n};\n" + }, + "version": "-7208318765-export type Wrap = {\n [K in keyof C]: { wrapped: C[K] }\n}\n", + "signature": "-7604652776-export type Wrap = {\n [K in keyof C]: {\n wrapped: C[K];\n };\n};\n" + }, + "../src/bug.js": { + "original": { + "version": "-25729561895-import * as B from \"./box.js\"\nimport * as W from \"./wrap.js\"\n\n/**\n * @template {object} C\n * @param {C} source\n * @returns {W.Wrap}\n */\nconst wrap = source => {\nthrow source\n}\n\n/**\n * @returns {B.Box}\n */\nconst box = (n = 0) => ({ unbox: () => n })\n\nexport const bug = wrap({ n: box(1) });\nexport const something = 1;", + "signature": "-7681488146-export const bug: W.Wrap<{\n n: B.Box;\n}>;\nexport const something: 1;\nimport * as B from \"./box.js\";\nimport * as W from \"./wrap.js\";\n" + }, + "version": "-25729561895-import * as B from \"./box.js\"\nimport * as W from \"./wrap.js\"\n\n/**\n * @template {object} C\n * @param {C} source\n * @returns {W.Wrap}\n */\nconst wrap = source => {\nthrow source\n}\n\n/**\n * @returns {B.Box}\n */\nconst box = (n = 0) => ({ unbox: () => n })\n\nexport const bug = wrap({ n: box(1) });\nexport const something = 1;", + "signature": "-7681488146-export const bug: W.Wrap<{\n n: B.Box;\n}>;\nexport const something: 1;\nimport * as B from \"./box.js\";\nimport * as W from \"./wrap.js\";\n" + } + }, + "root": [ + [ + [ + 2, + 4 + ], + [ + "../src/box.ts", + "../src/wrap.ts", + "../src/bug.js" + ] + ] + ], + "options": { + "checkJs": true, + "composite": true, + "outDir": "./" + }, + "referencedMap": { + "../src/bug.js": [ + "../src/box.ts", + "../src/wrap.ts" + ] + }, + "exportedModulesMap": {}, + "semanticDiagnosticsPerFile": [ + "../../../lib/lib.d.ts", + "../src/box.ts", + "../src/bug.js", + "../src/wrap.ts" + ], + "latestChangedDtsFile": "./src/bug.d.ts" + }, + "version": "FakeTSVersion", + "size": 1753 +} +