Skip to content

Commit 6107758

Browse files
committed
Fix interface range cache ignoring generic params in cache key
1 parent d45a28e commit 6107758

2 files changed

Lines changed: 149 additions & 2 deletions

File tree

lib/parse/ParameterResolver.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,33 @@ export class ParameterResolver {
254254
}
255255
}
256256

257+
/**
258+
* Hash the given parameter range to a unique string representation.
259+
* @param range An unresolved range.
260+
*/
261+
public hashParameterRangeUnresolved(range: ParameterRangeUnresolved): string {
262+
switch (range.type) {
263+
case 'undefined':
264+
return 'undefined';
265+
case 'interface':
266+
case 'genericTypeReference':
267+
case 'raw':
268+
case 'literal':
269+
case 'override':
270+
return `${range.type}:${range.value}`;
271+
case 'union':
272+
case 'intersection':
273+
case 'tuple':
274+
return `${range.type}:[${range.elements.map(element => this.hashParameterRangeUnresolved(element)).join(',')}]`;
275+
case 'rest':
276+
case 'array':
277+
case 'keyof':
278+
return `${range.type}:[${this.hashParameterRangeUnresolved(range.value)}]`;
279+
case 'hash':
280+
return `hash:${JSON.stringify(range.value)}`;
281+
}
282+
}
283+
257284
/**
258285
* Resolve a class or interface.
259286
* @param interfaceName A class or interface name.
@@ -275,7 +302,11 @@ export class ParameterResolver {
275302
genericTypeRemappings: Record<string, ParameterRangeUnresolved>,
276303
getNestedFields: boolean,
277304
): Promise<ParameterRangeResolved> {
278-
const cacheKey = `${interfaceName}::${(qualifiedPath || []).join('.')}::${owningClass.fileName}`;
305+
const cacheKeyGenerics = genericTypeParameterInstances ?
306+
genericTypeParameterInstances.map(genericTypeParameterInstance => this
307+
.hashParameterRangeUnresolved(genericTypeParameterInstance)).join(',') :
308+
'';
309+
const cacheKey = `${interfaceName}::${(qualifiedPath || []).join('.')}::${cacheKeyGenerics}::${owningClass.fileName}`;
279310
let resolved = this.cacheInterfaceRange.get(cacheKey);
280311
if (!resolved) {
281312
resolved = this.resolveRangeInterfaceInner(

test/parse/ParameterResolver.test.ts

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,103 @@ describe('ParameterResolver', () => {
536536
});
537537
});
538538

539+
describe('hashParameterRangeUnresolved', () => {
540+
it('should hash undefined', () => {
541+
expect(loader.hashParameterRangeUnresolved({ type: 'undefined' }))
542+
.toEqual('undefined');
543+
});
544+
545+
it('should hash interface', () => {
546+
expect(loader.hashParameterRangeUnresolved(<any> { type: 'interface', value: 'IFACE' }))
547+
.toEqual('interface:IFACE');
548+
});
549+
550+
it('should hash genericTypeReference', () => {
551+
expect(loader.hashParameterRangeUnresolved({ type: 'genericTypeReference', value: 'val' }))
552+
.toEqual('genericTypeReference:val');
553+
});
554+
555+
it('should hash raw', () => {
556+
expect(loader.hashParameterRangeUnresolved({ type: 'raw', value: 'boolean' }))
557+
.toEqual('raw:boolean');
558+
});
559+
560+
it('should hash literal', () => {
561+
expect(loader.hashParameterRangeUnresolved({ type: 'literal', value: 'val' }))
562+
.toEqual('literal:val');
563+
});
564+
565+
it('should hash override', () => {
566+
expect(loader.hashParameterRangeUnresolved({ type: 'override', value: 'val' }))
567+
.toEqual('override:val');
568+
});
569+
570+
it('should hash union', () => {
571+
expect(loader.hashParameterRangeUnresolved({
572+
type: 'union',
573+
elements: [
574+
{ type: 'raw', value: 'boolean' },
575+
{ type: 'raw', value: 'number' },
576+
],
577+
}))
578+
.toEqual('union:[raw:boolean,raw:number]');
579+
});
580+
581+
it('should hash intersection', () => {
582+
expect(loader.hashParameterRangeUnresolved({
583+
type: 'intersection',
584+
elements: [
585+
{ type: 'raw', value: 'boolean' },
586+
{ type: 'raw', value: 'number' },
587+
],
588+
}))
589+
.toEqual('intersection:[raw:boolean,raw:number]');
590+
});
591+
592+
it('should hash tuple', () => {
593+
expect(loader.hashParameterRangeUnresolved({
594+
type: 'tuple',
595+
elements: [
596+
{ type: 'raw', value: 'boolean' },
597+
{ type: 'raw', value: 'number' },
598+
],
599+
}))
600+
.toEqual('tuple:[raw:boolean,raw:number]');
601+
});
602+
603+
it('should hash rest', () => {
604+
expect(loader.hashParameterRangeUnresolved({
605+
type: 'rest',
606+
value: { type: 'raw', value: 'boolean' },
607+
}))
608+
.toEqual('rest:[raw:boolean]');
609+
});
610+
611+
it('should hash array', () => {
612+
expect(loader.hashParameterRangeUnresolved({
613+
type: 'array',
614+
value: { type: 'raw', value: 'boolean' },
615+
}))
616+
.toEqual('array:[raw:boolean]');
617+
});
618+
619+
it('should hash keyof', () => {
620+
expect(loader.hashParameterRangeUnresolved({
621+
type: 'keyof',
622+
value: { type: 'raw', value: 'boolean' },
623+
}))
624+
.toEqual('keyof:[raw:boolean]');
625+
});
626+
627+
it('should hash hash', () => {
628+
expect(loader.hashParameterRangeUnresolved({
629+
type: 'hash',
630+
value: <any> { a: 'b' },
631+
}))
632+
.toEqual('hash:{"a":"b"}');
633+
});
634+
});
635+
539636
describe('resolveRange', () => {
540637
const classReference: ClassReferenceLoaded = <any> { localName: 'A', fileName: 'A' };
541638

@@ -1246,7 +1343,26 @@ class ClassA {}
12461343
const second = await loader
12471344
.resolveRangeInterface('ClassA', undefined, undefined, classReference, classReference, {}, true);
12481345
expect(first).toBe(second);
1249-
expect((<any> loader).cacheInterfaceRange.keys()).toEqual([ 'ClassA::::A' ]);
1346+
expect((<any> loader).cacheInterfaceRange.keys()).toEqual([ 'ClassA::::::A' ]);
1347+
});
1348+
1349+
it('should resolve a class multiple times with different generics without cache', async() => {
1350+
resolutionContext.contentsOverrides = {
1351+
'A.d.ts': `
1352+
class ClassA {}
1353+
`,
1354+
};
1355+
const first = await loader
1356+
.resolveRangeInterface('ClassA', undefined, [
1357+
{ type: 'raw', value: 'boolean' },
1358+
], classReference, classReference, {}, true);
1359+
const second = await loader
1360+
.resolveRangeInterface('ClassA', undefined, [
1361+
{ type: 'raw', value: 'number' },
1362+
], classReference, classReference, {}, true);
1363+
expect(first).not.toBe(second);
1364+
expect((<any> loader).cacheInterfaceRange.keys())
1365+
.toEqual([ 'ClassA::::raw:number::A', 'ClassA::::raw:boolean::A' ]);
12501366
});
12511367

12521368
it('should resolve an implicit class', async() => {

0 commit comments

Comments
 (0)