Skip to content

Commit 48a8e89

Browse files
authoredSep 19, 2022
Improve check of whether type query node possibly contains reference to type parameter (#50070)
* WIP * implement typequery contains reference check + tests * add unit test * fix unit test * use symbols in scope to check type query type parameter references * remove comment on unit test * remove comment * use isNodeDescendantOf implementation to check scoping * CR: small fixes * treat the different kinds of type parameter declarations * undo test change
1 parent af9ced1 commit 48a8e89

File tree

8 files changed

+315
-2
lines changed

8 files changed

+315
-2
lines changed
 

‎src/compiler/checker.ts

+26-2
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,7 @@ namespace ts {
728728
isPropertyAccessible,
729729
getTypeOnlyAliasDeclaration,
730730
getMemberOverrideModifierStatus,
731+
isTypeParameterPossiblyReferenced,
731732
};
732733

733734
function runWithInferenceBlockedFromSourceNode<T>(node: Node | undefined, fn: () => T): T {
@@ -17193,9 +17194,10 @@ namespace ts {
1719317194
}
1719417195

1719517196
function isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node) {
17196-
// If the type parameter doesn't have exactly one declaration, if there are invening statement blocks
17197+
// If the type parameter doesn't have exactly one declaration, if there are intervening statement blocks
1719717198
// between the node and the type parameter declaration, if the node contains actual references to the
17198-
// type parameter, or if the node contains type queries, we consider the type parameter possibly referenced.
17199+
// type parameter, or if the node contains type queries that we can't prove couldn't contain references to the type parameter,
17200+
// we consider the type parameter possibly referenced.
1719917201
if (tp.symbol && tp.symbol.declarations && tp.symbol.declarations.length === 1) {
1720017202
const container = tp.symbol.declarations[0].parent;
1720117203
for (let n = node; n !== container; n = n.parent) {
@@ -17214,6 +17216,28 @@ namespace ts {
1721417216
return !tp.isThisType && isPartOfTypeNode(node) && maybeTypeParameterReference(node) &&
1721517217
getTypeFromTypeNodeWorker(node as TypeNode) === tp; // use worker because we're looking for === equality
1721617218
case SyntaxKind.TypeQuery:
17219+
const entityName = (node as TypeQueryNode).exprName;
17220+
const firstIdentifier = getFirstIdentifier(entityName);
17221+
const firstIdentifierSymbol = getResolvedSymbol(firstIdentifier);
17222+
const tpDeclaration = tp.symbol.declarations![0]; // There is exactly one declaration, otherwise `containsReference` is not called
17223+
let tpScope: Node;
17224+
if (tpDeclaration.kind === SyntaxKind.TypeParameter) { // Type parameter is a regular type parameter, e.g. foo<T>
17225+
tpScope = tpDeclaration.parent;
17226+
}
17227+
else if (tp.isThisType) {
17228+
// Type parameter is the this type, and its declaration is the class declaration.
17229+
tpScope = tpDeclaration;
17230+
}
17231+
else {
17232+
// Type parameter's declaration was unrecognized.
17233+
// This could happen if the type parameter comes from e.g. a JSDoc annotation, so we default to returning true.
17234+
return true;
17235+
}
17236+
17237+
if (firstIdentifierSymbol.declarations) {
17238+
return some(firstIdentifierSymbol.declarations, idDecl => isNodeDescendantOf(idDecl, tpScope)) ||
17239+
some((node as TypeQueryNode).typeArguments, containsReference);
17240+
}
1721717241
return true;
1721817242
case SyntaxKind.MethodDeclaration:
1721917243
case SyntaxKind.MethodSignature:

‎src/compiler/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4854,6 +4854,7 @@ namespace ts {
48544854
/* @internal */ isPropertyAccessible(node: Node, isSuper: boolean, isWrite: boolean, containingType: Type, property: Symbol): boolean;
48554855
/* @internal */ getTypeOnlyAliasDeclaration(symbol: Symbol): TypeOnlyAliasDeclaration | undefined;
48564856
/* @internal */ getMemberOverrideModifierStatus(node: ClassLikeDeclaration, member: ClassElement): MemberOverrideStatus;
4857+
/* @internal */ isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node): boolean;
48574858
}
48584859

48594860
/* @internal */

‎src/testRunner/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"unittests/reuseProgramStructure.ts",
7777
"unittests/semver.ts",
7878
"unittests/transform.ts",
79+
"unittests/typeParameterIsPossiblyReferenced.ts",
7980
"unittests/config/commandLineParsing.ts",
8081
"unittests/config/configurationExtension.ts",
8182
"unittests/config/convertCompilerOptionsFromJson.ts",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
describe("unittests :: internalApi :: typeParameterIsPossiblyReferenced", () => {
2+
it("with type parameter aliasing", () => {
3+
const content = `
4+
declare function foo<T>(b: T, f: <T>(a: typeof b) => typeof a): typeof f;
5+
`;
6+
const host = new fakes.CompilerHost(vfs.createFromFileSystem(
7+
Harness.IO,
8+
/*ignoreCases*/ true,
9+
{
10+
documents: [
11+
new documents.TextDocument("/file.ts", content)
12+
],
13+
cwd: "/",
14+
}
15+
));
16+
const program = ts.createProgram({
17+
host,
18+
rootNames: ["/file.ts"],
19+
options: { strict: true },
20+
});
21+
const checker = program.getTypeChecker();
22+
const file = program.getSourceFile("/file.ts")!;
23+
const typeQueryNode = (((file.statements[0] as ts.FunctionDeclaration) // function f<T>
24+
.parameters[1] // f
25+
.type! as ts.FunctionTypeNode) // <T>(a: typeof b) => typeof a
26+
.type as ts.TypeQueryNode) // typeof a
27+
;
28+
const typeParameterDecl = (file.statements[0] as ts.FunctionDeclaration).typeParameters![0]; // T in f<T>
29+
const typeParameter = checker.getTypeAtLocation(typeParameterDecl)! as ts.TypeParameter;
30+
const isReferenced = checker.isTypeParameterPossiblyReferenced(typeParameter, typeQueryNode);
31+
assert.ok(isReferenced, "Type parameter is referenced in type query node");
32+
});
33+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//// [typeofObjectInference.ts]
2+
let val = 1
3+
4+
function decorateA<O extends any>(fn: (first: {value: typeof val}) => O) {
5+
return (): O => fn({value: val})
6+
}
7+
let a = decorateA(({value}) => 5)
8+
9+
function decorateB<O extends any>(fn: (first: typeof val) => O) {
10+
return (): O => fn(val)
11+
}
12+
let b = decorateB((value) => 5)
13+
14+
function decorateC<O extends any>(fn: (first: {value: number}) => O) {
15+
return (): O => fn({value: val})
16+
}
17+
let c = decorateC(({value}) => 5)
18+
19+
type First = {value: typeof val}
20+
function decorateD<O extends any>(fn: (first: First) => O) {
21+
return (): O => fn({value: val})
22+
}
23+
let d = decorateD(({value}) => 5)
24+
25+
//// [typeofObjectInference.js]
26+
var val = 1;
27+
function decorateA(fn) {
28+
return function () { return fn({ value: val }); };
29+
}
30+
var a = decorateA(function (_a) {
31+
var value = _a.value;
32+
return 5;
33+
});
34+
function decorateB(fn) {
35+
return function () { return fn(val); };
36+
}
37+
var b = decorateB(function (value) { return 5; });
38+
function decorateC(fn) {
39+
return function () { return fn({ value: val }); };
40+
}
41+
var c = decorateC(function (_a) {
42+
var value = _a.value;
43+
return 5;
44+
});
45+
function decorateD(fn) {
46+
return function () { return fn({ value: val }); };
47+
}
48+
var d = decorateD(function (_a) {
49+
var value = _a.value;
50+
return 5;
51+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
=== tests/cases/compiler/typeofObjectInference.ts ===
2+
let val = 1
3+
>val : Symbol(val, Decl(typeofObjectInference.ts, 0, 3))
4+
5+
function decorateA<O extends any>(fn: (first: {value: typeof val}) => O) {
6+
>decorateA : Symbol(decorateA, Decl(typeofObjectInference.ts, 0, 11))
7+
>O : Symbol(O, Decl(typeofObjectInference.ts, 2, 19))
8+
>fn : Symbol(fn, Decl(typeofObjectInference.ts, 2, 34))
9+
>first : Symbol(first, Decl(typeofObjectInference.ts, 2, 39))
10+
>value : Symbol(value, Decl(typeofObjectInference.ts, 2, 47))
11+
>val : Symbol(val, Decl(typeofObjectInference.ts, 0, 3))
12+
>O : Symbol(O, Decl(typeofObjectInference.ts, 2, 19))
13+
14+
return (): O => fn({value: val})
15+
>O : Symbol(O, Decl(typeofObjectInference.ts, 2, 19))
16+
>fn : Symbol(fn, Decl(typeofObjectInference.ts, 2, 34))
17+
>value : Symbol(value, Decl(typeofObjectInference.ts, 3, 24))
18+
>val : Symbol(val, Decl(typeofObjectInference.ts, 0, 3))
19+
}
20+
let a = decorateA(({value}) => 5)
21+
>a : Symbol(a, Decl(typeofObjectInference.ts, 5, 3))
22+
>decorateA : Symbol(decorateA, Decl(typeofObjectInference.ts, 0, 11))
23+
>value : Symbol(value, Decl(typeofObjectInference.ts, 5, 20))
24+
25+
function decorateB<O extends any>(fn: (first: typeof val) => O) {
26+
>decorateB : Symbol(decorateB, Decl(typeofObjectInference.ts, 5, 33))
27+
>O : Symbol(O, Decl(typeofObjectInference.ts, 7, 19))
28+
>fn : Symbol(fn, Decl(typeofObjectInference.ts, 7, 34))
29+
>first : Symbol(first, Decl(typeofObjectInference.ts, 7, 39))
30+
>val : Symbol(val, Decl(typeofObjectInference.ts, 0, 3))
31+
>O : Symbol(O, Decl(typeofObjectInference.ts, 7, 19))
32+
33+
return (): O => fn(val)
34+
>O : Symbol(O, Decl(typeofObjectInference.ts, 7, 19))
35+
>fn : Symbol(fn, Decl(typeofObjectInference.ts, 7, 34))
36+
>val : Symbol(val, Decl(typeofObjectInference.ts, 0, 3))
37+
}
38+
let b = decorateB((value) => 5)
39+
>b : Symbol(b, Decl(typeofObjectInference.ts, 10, 3))
40+
>decorateB : Symbol(decorateB, Decl(typeofObjectInference.ts, 5, 33))
41+
>value : Symbol(value, Decl(typeofObjectInference.ts, 10, 19))
42+
43+
function decorateC<O extends any>(fn: (first: {value: number}) => O) {
44+
>decorateC : Symbol(decorateC, Decl(typeofObjectInference.ts, 10, 31))
45+
>O : Symbol(O, Decl(typeofObjectInference.ts, 12, 19))
46+
>fn : Symbol(fn, Decl(typeofObjectInference.ts, 12, 34))
47+
>first : Symbol(first, Decl(typeofObjectInference.ts, 12, 39))
48+
>value : Symbol(value, Decl(typeofObjectInference.ts, 12, 47))
49+
>O : Symbol(O, Decl(typeofObjectInference.ts, 12, 19))
50+
51+
return (): O => fn({value: val})
52+
>O : Symbol(O, Decl(typeofObjectInference.ts, 12, 19))
53+
>fn : Symbol(fn, Decl(typeofObjectInference.ts, 12, 34))
54+
>value : Symbol(value, Decl(typeofObjectInference.ts, 13, 24))
55+
>val : Symbol(val, Decl(typeofObjectInference.ts, 0, 3))
56+
}
57+
let c = decorateC(({value}) => 5)
58+
>c : Symbol(c, Decl(typeofObjectInference.ts, 15, 3))
59+
>decorateC : Symbol(decorateC, Decl(typeofObjectInference.ts, 10, 31))
60+
>value : Symbol(value, Decl(typeofObjectInference.ts, 15, 20))
61+
62+
type First = {value: typeof val}
63+
>First : Symbol(First, Decl(typeofObjectInference.ts, 15, 33))
64+
>value : Symbol(value, Decl(typeofObjectInference.ts, 17, 14))
65+
>val : Symbol(val, Decl(typeofObjectInference.ts, 0, 3))
66+
67+
function decorateD<O extends any>(fn: (first: First) => O) {
68+
>decorateD : Symbol(decorateD, Decl(typeofObjectInference.ts, 17, 32))
69+
>O : Symbol(O, Decl(typeofObjectInference.ts, 18, 19))
70+
>fn : Symbol(fn, Decl(typeofObjectInference.ts, 18, 34))
71+
>first : Symbol(first, Decl(typeofObjectInference.ts, 18, 39))
72+
>First : Symbol(First, Decl(typeofObjectInference.ts, 15, 33))
73+
>O : Symbol(O, Decl(typeofObjectInference.ts, 18, 19))
74+
75+
return (): O => fn({value: val})
76+
>O : Symbol(O, Decl(typeofObjectInference.ts, 18, 19))
77+
>fn : Symbol(fn, Decl(typeofObjectInference.ts, 18, 34))
78+
>value : Symbol(value, Decl(typeofObjectInference.ts, 19, 24))
79+
>val : Symbol(val, Decl(typeofObjectInference.ts, 0, 3))
80+
}
81+
let d = decorateD(({value}) => 5)
82+
>d : Symbol(d, Decl(typeofObjectInference.ts, 21, 3))
83+
>decorateD : Symbol(decorateD, Decl(typeofObjectInference.ts, 17, 32))
84+
>value : Symbol(value, Decl(typeofObjectInference.ts, 21, 20))
85+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
=== tests/cases/compiler/typeofObjectInference.ts ===
2+
let val = 1
3+
>val : number
4+
>1 : 1
5+
6+
function decorateA<O extends any>(fn: (first: {value: typeof val}) => O) {
7+
>decorateA : <O extends unknown>(fn: (first: { value: typeof val;}) => O) => () => O
8+
>fn : (first: { value: typeof val;}) => O
9+
>first : { value: typeof val; }
10+
>value : number
11+
>val : number
12+
13+
return (): O => fn({value: val})
14+
>(): O => fn({value: val}) : () => O
15+
>fn({value: val}) : O
16+
>fn : (first: { value: number; }) => O
17+
>{value: val} : { value: number; }
18+
>value : number
19+
>val : number
20+
}
21+
let a = decorateA(({value}) => 5)
22+
>a : () => number
23+
>decorateA(({value}) => 5) : () => number
24+
>decorateA : <O extends unknown>(fn: (first: { value: number; }) => O) => () => O
25+
>({value}) => 5 : ({ value }: { value: number; }) => number
26+
>value : number
27+
>5 : 5
28+
29+
function decorateB<O extends any>(fn: (first: typeof val) => O) {
30+
>decorateB : <O extends unknown>(fn: (first: typeof val) => O) => () => O
31+
>fn : (first: typeof val) => O
32+
>first : number
33+
>val : number
34+
35+
return (): O => fn(val)
36+
>(): O => fn(val) : () => O
37+
>fn(val) : O
38+
>fn : (first: number) => O
39+
>val : number
40+
}
41+
let b = decorateB((value) => 5)
42+
>b : () => number
43+
>decorateB((value) => 5) : () => number
44+
>decorateB : <O extends unknown>(fn: (first: number) => O) => () => O
45+
>(value) => 5 : (value: number) => number
46+
>value : number
47+
>5 : 5
48+
49+
function decorateC<O extends any>(fn: (first: {value: number}) => O) {
50+
>decorateC : <O extends unknown>(fn: (first: { value: number;}) => O) => () => O
51+
>fn : (first: { value: number;}) => O
52+
>first : { value: number; }
53+
>value : number
54+
55+
return (): O => fn({value: val})
56+
>(): O => fn({value: val}) : () => O
57+
>fn({value: val}) : O
58+
>fn : (first: { value: number; }) => O
59+
>{value: val} : { value: number; }
60+
>value : number
61+
>val : number
62+
}
63+
let c = decorateC(({value}) => 5)
64+
>c : () => number
65+
>decorateC(({value}) => 5) : () => number
66+
>decorateC : <O extends unknown>(fn: (first: { value: number; }) => O) => () => O
67+
>({value}) => 5 : ({ value }: { value: number; }) => number
68+
>value : number
69+
>5 : 5
70+
71+
type First = {value: typeof val}
72+
>First : { value: typeof val; }
73+
>value : number
74+
>val : number
75+
76+
function decorateD<O extends any>(fn: (first: First) => O) {
77+
>decorateD : <O extends unknown>(fn: (first: First) => O) => () => O
78+
>fn : (first: First) => O
79+
>first : First
80+
81+
return (): O => fn({value: val})
82+
>(): O => fn({value: val}) : () => O
83+
>fn({value: val}) : O
84+
>fn : (first: First) => O
85+
>{value: val} : { value: number; }
86+
>value : number
87+
>val : number
88+
}
89+
let d = decorateD(({value}) => 5)
90+
>d : () => number
91+
>decorateD(({value}) => 5) : () => number
92+
>decorateD : <O extends unknown>(fn: (first: First) => O) => () => O
93+
>({value}) => 5 : ({ value }: First) => number
94+
>value : number
95+
>5 : 5
96+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
let val = 1
2+
3+
function decorateA<O extends any>(fn: (first: {value: typeof val}) => O) {
4+
return (): O => fn({value: val})
5+
}
6+
let a = decorateA(({value}) => 5)
7+
8+
function decorateB<O extends any>(fn: (first: typeof val) => O) {
9+
return (): O => fn(val)
10+
}
11+
let b = decorateB((value) => 5)
12+
13+
function decorateC<O extends any>(fn: (first: {value: number}) => O) {
14+
return (): O => fn({value: val})
15+
}
16+
let c = decorateC(({value}) => 5)
17+
18+
type First = {value: typeof val}
19+
function decorateD<O extends any>(fn: (first: First) => O) {
20+
return (): O => fn({value: val})
21+
}
22+
let d = decorateD(({value}) => 5)

0 commit comments

Comments
 (0)
Please sign in to comment.