Skip to content

Commit 0f4e922

Browse files
committed
Cache accessibe symbol chains, type parameter name generation
1 parent 456806b commit 0f4e922

File tree

6 files changed

+181
-9
lines changed

6 files changed

+181
-9
lines changed

src/compiler/checker.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3935,12 +3935,12 @@ namespace ts {
39353935
return typeCopy;
39363936
}
39373937

3938-
function forEachSymbolTableInScope<T>(enclosingDeclaration: Node | undefined, callback: (symbolTable: SymbolTable) => T): T {
3938+
function forEachSymbolTableInScope<T>(enclosingDeclaration: Node | undefined, callback: (symbolTable: SymbolTable, ignoreQualification?: boolean, scopeNode?: Node) => T): T {
39393939
let result: T;
39403940
for (let location = enclosingDeclaration; location; location = location.parent) {
39413941
// Locals of a source file are not in scope (because they get merged into the global symbol table)
39423942
if (location.locals && !isGlobalSourceFile(location)) {
3943-
if (result = callback(location.locals)) {
3943+
if (result = callback(location.locals, /*ignoreQualification*/ undefined, location)) {
39443944
return result;
39453945
}
39463946
}
@@ -3955,7 +3955,7 @@ namespace ts {
39553955
// `sym` may not have exports if this module declaration is backed by the symbol for a `const` that's being rewritten
39563956
// into a namespace - in such cases, it's best to just let the namespace appear empty (the const members couldn't have referred
39573957
// to one another anyway)
3958-
if (result = callback(sym?.exports || emptySymbols)) {
3958+
if (result = callback(sym?.exports || emptySymbols, /*ignoreQualification*/ undefined, location)) {
39593959
return result;
39603960
}
39613961
break;
@@ -3976,7 +3976,7 @@ namespace ts {
39763976
(table || (table = createSymbolTable())).set(key, memberSymbol);
39773977
}
39783978
});
3979-
if (table && (result = callback(table))) {
3979+
if (table && (result = callback(table, /*ignoreQualification*/ undefined, location))) {
39803980
return result;
39813981
}
39823982
break;
@@ -3995,13 +3995,23 @@ namespace ts {
39953995
if (!(symbol && !isPropertyOrMethodDeclarationSymbol(symbol))) {
39963996
return undefined;
39973997
}
3998+
const links = getSymbolLinks(symbol);
3999+
const cache = (links.accessibleChainCache ||= new Map());
4000+
// Go from enclosingDeclaration to the first scope we check, so the cache is keyed off the scope and thus shared more
4001+
const firstRelevantLocation = forEachSymbolTableInScope(enclosingDeclaration, (_, __, node) => node);
4002+
const key = `${useOnlyExternalAliasing ? 0 : 1}|${firstRelevantLocation && getNodeId(firstRelevantLocation)}|${meaning}`;
4003+
if (cache.has(key)) {
4004+
return cache.get(key);
4005+
}
39984006

39994007
const id = getSymbolId(symbol);
40004008
let visitedSymbolTables = visitedSymbolTablesMap.get(id);
40014009
if (!visitedSymbolTables) {
40024010
visitedSymbolTablesMap.set(id, visitedSymbolTables = []);
40034011
}
4004-
return forEachSymbolTableInScope(enclosingDeclaration, getAccessibleSymbolChainFromSymbolTable);
4012+
const result = forEachSymbolTableInScope(enclosingDeclaration, getAccessibleSymbolChainFromSymbolTable);
4013+
cache.set(key, result);
4014+
return result;
40054015

40064016
/**
40074017
* @param {ignoreQualification} boolean Set when a symbol is being looked for through the exports of another symbol (meaning we have a route to qualify it already)
@@ -5814,7 +5824,7 @@ namespace ts {
58145824
}
58155825
if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams) {
58165826
const rawtext = result.escapedText as string;
5817-
let i = 0;
5827+
let i = context.typeParameterNamesByTextNextNameCount?.get(rawtext) || 0;
58185828
let text = rawtext;
58195829
while (context.typeParameterNamesByText?.has(text) || typeParameterShadowsNameInScope(text as __String, context, type)) {
58205830
i++;
@@ -5823,8 +5833,11 @@ namespace ts {
58235833
if (text !== rawtext) {
58245834
result = factory.createIdentifier(text, result.typeArguments);
58255835
}
5826-
(context.typeParameterNames || (context.typeParameterNames = new Map())).set(getTypeId(type), result);
5827-
(context.typeParameterNamesByText || (context.typeParameterNamesByText = new Set())).add(result.escapedText as string);
5836+
// avoiding iterations of the above loop turns out to be worth it when `i` starts to get large, so we cache the max
5837+
// `i` we've used thus far, to save work later
5838+
(context.typeParameterNamesByTextNextNameCount ||= new Map()).set(rawtext, i);
5839+
(context.typeParameterNames ||= new Map()).set(getTypeId(type), result);
5840+
(context.typeParameterNamesByText ||= new Set()).add(result.escapedText as string);
58285841
}
58295842
return result;
58305843
}
@@ -7737,6 +7750,7 @@ namespace ts {
77377750
typeParameterSymbolList?: Set<number>;
77387751
typeParameterNames?: ESMap<TypeId, Identifier>;
77397752
typeParameterNamesByText?: Set<string>;
7753+
typeParameterNamesByTextNextNameCount?: ESMap<string, number>;
77407754
usedSymbolNames?: Set<string>;
77417755
remappedSymbolNames?: ESMap<SymbolId, string>;
77427756
reverseMappedStack?: ReverseMappedSymbol[];

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4831,6 +4831,7 @@ namespace ts {
48314831
typeOnlyDeclaration?: TypeOnlyCompatibleAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs
48324832
isConstructorDeclaredProperty?: boolean; // Property declared through 'this.x = ...' assignment in constructor
48334833
tupleLabelDeclaration?: NamedTupleMember | ParameterDeclaration; // Declaration associated with the tuple's label
4834+
accessibleChainCache?: ESMap<string, Symbol[] | undefined>;
48344835
}
48354836

48364837
/* @internal */

src/harness/compilerImpl.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,11 @@ namespace compiler {
254254
if (compilerOptions.skipDefaultLibCheck === undefined) compilerOptions.skipDefaultLibCheck = true;
255255
if (compilerOptions.noErrorTruncation === undefined) compilerOptions.noErrorTruncation = true;
256256

257-
const preProgram = ts.length(rootFiles) < 100 ? ts.createProgram(rootFiles || [], { ...compilerOptions, configFile: compilerOptions.configFile, traceResolution: false }, host) : undefined;
257+
// pre-emit/post-emit error comparison requires declaration emit twice, which can be slow. If it's unlikely to flag any error consistency issues
258+
// and if the test is running `skipLibCheck` - an indicator that we want the tets to run quickly - skip the before/after error comparison, too
259+
const skipErrorComparison = ts.length(rootFiles) >= 100 || (!!compilerOptions.skipLibCheck && !!compilerOptions.declaration);
260+
261+
const preProgram = !skipErrorComparison ? ts.createProgram(rootFiles || [], { ...compilerOptions, configFile: compilerOptions.configFile, traceResolution: false }, host) : undefined;
258262
const preErrors = preProgram && ts.getPreEmitDiagnostics(preProgram);
259263

260264
const program = ts.createProgram(rootFiles || [], compilerOptions, host);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
tests/cases/compiler/Api.ts(6,5): error TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed.
2+
tests/cases/compiler/Api.ts(7,5): error TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed.
3+
tests/cases/compiler/Api.ts(8,5): error TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed.
4+
5+
6+
==== tests/cases/compiler/http-client.ts (0 errors) ====
7+
type TPromise<ResolveType, RejectType = any> = Omit<Promise<ResolveType>, "then" | "catch"> & {
8+
then<TResult1 = ResolveType, TResult2 = never>(
9+
onfulfilled?: ((value: ResolveType) => TResult1 | PromiseLike<TResult1>) | undefined | null,
10+
onrejected?: ((reason: RejectType) => TResult2 | PromiseLike<TResult2>) | undefined | null,
11+
): TPromise<TResult1 | TResult2, RejectType>;
12+
catch<TResult = never>(
13+
onrejected?: ((reason: RejectType) => TResult | PromiseLike<TResult>) | undefined | null,
14+
): TPromise<ResolveType | TResult, RejectType>;
15+
};
16+
17+
export interface HttpResponse<D extends unknown, E extends unknown = unknown> extends Response {
18+
data: D;
19+
error: E;
20+
}
21+
22+
export class HttpClient<SecurityDataType = unknown> {
23+
public request = <T = any, E = any>(): TPromise<HttpResponse<T, E>> => {
24+
return '' as any;
25+
};
26+
}
27+
==== tests/cases/compiler/Api.ts (3 errors) ====
28+
import { HttpClient } from "./http-client";
29+
30+
export class Api<SecurityDataType = unknown> {
31+
constructor(private http: HttpClient<SecurityDataType>) { }
32+
33+
abc1 = () => this.http.request();
34+
~~~~
35+
!!! error TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed.
36+
abc2 = () => this.http.request();
37+
~~~~
38+
!!! error TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed.
39+
abc3 = () => this.http.request();
40+
~~~~
41+
!!! error TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed.
42+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//// [tests/cases/compiler/declarationEmitPrivatePromiseLikeInterface.ts] ////
2+
3+
//// [http-client.ts]
4+
type TPromise<ResolveType, RejectType = any> = Omit<Promise<ResolveType>, "then" | "catch"> & {
5+
then<TResult1 = ResolveType, TResult2 = never>(
6+
onfulfilled?: ((value: ResolveType) => TResult1 | PromiseLike<TResult1>) | undefined | null,
7+
onrejected?: ((reason: RejectType) => TResult2 | PromiseLike<TResult2>) | undefined | null,
8+
): TPromise<TResult1 | TResult2, RejectType>;
9+
catch<TResult = never>(
10+
onrejected?: ((reason: RejectType) => TResult | PromiseLike<TResult>) | undefined | null,
11+
): TPromise<ResolveType | TResult, RejectType>;
12+
};
13+
14+
export interface HttpResponse<D extends unknown, E extends unknown = unknown> extends Response {
15+
data: D;
16+
error: E;
17+
}
18+
19+
export class HttpClient<SecurityDataType = unknown> {
20+
public request = <T = any, E = any>(): TPromise<HttpResponse<T, E>> => {
21+
return '' as any;
22+
};
23+
}
24+
//// [Api.ts]
25+
import { HttpClient } from "./http-client";
26+
27+
export class Api<SecurityDataType = unknown> {
28+
constructor(private http: HttpClient<SecurityDataType>) { }
29+
30+
abc1 = () => this.http.request();
31+
abc2 = () => this.http.request();
32+
abc3 = () => this.http.request();
33+
}
34+
35+
//// [http-client.js]
36+
"use strict";
37+
exports.__esModule = true;
38+
exports.HttpClient = void 0;
39+
var HttpClient = /** @class */ (function () {
40+
function HttpClient() {
41+
this.request = function () {
42+
return '';
43+
};
44+
}
45+
return HttpClient;
46+
}());
47+
exports.HttpClient = HttpClient;
48+
//// [Api.js]
49+
"use strict";
50+
exports.__esModule = true;
51+
exports.Api = void 0;
52+
var Api = /** @class */ (function () {
53+
function Api(http) {
54+
var _this = this;
55+
this.http = http;
56+
this.abc1 = function () { return _this.http.request(); };
57+
this.abc2 = function () { return _this.http.request(); };
58+
this.abc3 = function () { return _this.http.request(); };
59+
}
60+
return Api;
61+
}());
62+
exports.Api = Api;
63+
64+
65+
//// [http-client.d.ts]
66+
declare type TPromise<ResolveType, RejectType = any> = Omit<Promise<ResolveType>, "then" | "catch"> & {
67+
then<TResult1 = ResolveType, TResult2 = never>(onfulfilled?: ((value: ResolveType) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: RejectType) => TResult2 | PromiseLike<TResult2>) | undefined | null): TPromise<TResult1 | TResult2, RejectType>;
68+
catch<TResult = never>(onrejected?: ((reason: RejectType) => TResult | PromiseLike<TResult>) | undefined | null): TPromise<ResolveType | TResult, RejectType>;
69+
};
70+
export interface HttpResponse<D extends unknown, E extends unknown = unknown> extends Response {
71+
data: D;
72+
error: E;
73+
}
74+
export declare class HttpClient<SecurityDataType = unknown> {
75+
request: <T = any, E = any>() => TPromise<HttpResponse<T, E>, any>;
76+
}
77+
export {};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// @declaration: true
2+
// @skipLibCheck: true
3+
// @noTypesAndSymbols: true
4+
// @filename: http-client.ts
5+
type TPromise<ResolveType, RejectType = any> = Omit<Promise<ResolveType>, "then" | "catch"> & {
6+
then<TResult1 = ResolveType, TResult2 = never>(
7+
onfulfilled?: ((value: ResolveType) => TResult1 | PromiseLike<TResult1>) | undefined | null,
8+
onrejected?: ((reason: RejectType) => TResult2 | PromiseLike<TResult2>) | undefined | null,
9+
): TPromise<TResult1 | TResult2, RejectType>;
10+
catch<TResult = never>(
11+
onrejected?: ((reason: RejectType) => TResult | PromiseLike<TResult>) | undefined | null,
12+
): TPromise<ResolveType | TResult, RejectType>;
13+
};
14+
15+
export interface HttpResponse<D extends unknown, E extends unknown = unknown> extends Response {
16+
data: D;
17+
error: E;
18+
}
19+
20+
export class HttpClient<SecurityDataType = unknown> {
21+
public request = <T = any, E = any>(): TPromise<HttpResponse<T, E>> => {
22+
return '' as any;
23+
};
24+
}
25+
// @filename: Api.ts
26+
import { HttpClient } from "./http-client";
27+
28+
export class Api<SecurityDataType = unknown> {
29+
constructor(private http: HttpClient<SecurityDataType>) { }
30+
31+
abc1 = () => this.http.request();
32+
abc2 = () => this.http.request();
33+
abc3 = () => this.http.request();
34+
}

0 commit comments

Comments
 (0)