Skip to content

Commit 8dbb2cd

Browse files
authored
allow this in typeQuery (#43898)
* allow `this` in typeQuery * add tests * get this type as expression * handle nested nodes * update baselines
1 parent d46d82c commit 8dbb2cd

17 files changed

+1282
-21
lines changed

src/compiler/checker.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13197,7 +13197,8 @@ namespace ts {
1319713197
// The expression is processed as an identifier expression (section 4.3)
1319813198
// or property access expression(section 4.10),
1319913199
// the widened type(section 3.9) of which becomes the result.
13200-
links.resolvedType = getRegularTypeOfLiteralType(getWidenedType(checkExpression(node.exprName)));
13200+
const type = isThisIdentifier(node.exprName) ? checkThisExpression(node.exprName) : checkExpression(node.exprName);
13201+
links.resolvedType = getRegularTypeOfLiteralType(getWidenedType(type));
1320113202
}
1320213203
return links.resolvedType;
1320313204
}
@@ -21998,9 +21999,11 @@ namespace ts {
2199821999
&& (source as MetaProperty).name.escapedText === (target as MetaProperty).name.escapedText;
2199922000
case SyntaxKind.Identifier:
2200022001
case SyntaxKind.PrivateIdentifier:
22001-
return target.kind === SyntaxKind.Identifier && getResolvedSymbol(source as Identifier) === getResolvedSymbol(target as Identifier) ||
22002-
(target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) &&
22003-
getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfNode(target);
22002+
return isThisInTypeQuery(source) ?
22003+
target.kind === SyntaxKind.ThisKeyword :
22004+
target.kind === SyntaxKind.Identifier && getResolvedSymbol(source as Identifier) === getResolvedSymbol(target as Identifier) ||
22005+
(target.kind === SyntaxKind.VariableDeclaration || target.kind === SyntaxKind.BindingElement) &&
22006+
getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(source as Identifier)) === getSymbolOfNode(target);
2200422007
case SyntaxKind.ThisKeyword:
2200522008
return target.kind === SyntaxKind.ThisKeyword;
2200622009
case SyntaxKind.SuperKeyword:
@@ -24538,6 +24541,7 @@ namespace ts {
2453824541
}
2453924542

2454024543
function checkThisExpression(node: Node): Type {
24544+
const isNodeInTypeQuery = isInTypeQuery(node);
2454124545
// Stop at the first arrow function so that we can
2454224546
// tell whether 'this' needs to be captured.
2454324547
let container = getThisContainer(node, /* includeArrowFunctions */ true);
@@ -24581,7 +24585,7 @@ namespace ts {
2458124585
}
2458224586

2458324587
// When targeting es6, mark that we'll need to capture `this` in its lexically bound scope.
24584-
if (capturedByArrowFunction && languageVersion < ScriptTarget.ES2015) {
24588+
if (!isNodeInTypeQuery && capturedByArrowFunction && languageVersion < ScriptTarget.ES2015) {
2458524589
captureLexicalThis(node, container);
2458624590
}
2458724591

@@ -27284,7 +27288,8 @@ namespace ts {
2728427288
}
2728527289

2728627290
function checkQualifiedName(node: QualifiedName, checkMode: CheckMode | undefined) {
27287-
return checkPropertyAccessExpressionOrQualifiedName(node, node.left, checkNonNullExpression(node.left), node.right, checkMode);
27291+
const leftType = isPartOfTypeQuery(node) && isThisIdentifier(node.left) ? checkNonNullType(checkThisExpression(node.left), node.left) : checkNonNullExpression(node.left);
27292+
return checkPropertyAccessExpressionOrQualifiedName(node, node.left, leftType, node.right, checkMode);
2728827293
}
2728927294

2729027295
function isMethodAccessForCall(node: Node) {

src/compiler/utilities.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4348,6 +4348,18 @@ namespace ts {
43484348
return !!node && node.kind === SyntaxKind.Identifier && identifierIsThisKeyword(node as Identifier);
43494349
}
43504350

4351+
export function isThisInTypeQuery(node: Node): boolean {
4352+
if (!isThisIdentifier(node)) {
4353+
return false;
4354+
}
4355+
4356+
while (isQualifiedName(node.parent) && node.parent.left === node) {
4357+
node = node.parent;
4358+
}
4359+
4360+
return node.parent.kind === SyntaxKind.TypeQuery;
4361+
}
4362+
43514363
export function identifierIsThisKeyword(id: Identifier): boolean {
43524364
return id.originalKeywordKind === SyntaxKind.ThisKeyword;
43534365
}

tests/baselines/reference/initializerReferencingConstructorLocals.errors.txt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorLocals.ts(4,9): error TS2304: Cannot find name 'z'.
22
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorLocals.ts(5,15): error TS2304: Cannot find name 'z'.
33
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorLocals.ts(6,14): error TS2339: Property 'z' does not exist on type 'C'.
4-
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorLocals.ts(7,15): error TS2304: Cannot find name 'this'.
4+
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorLocals.ts(7,20): error TS2339: Property 'z' does not exist on type 'C'.
55
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorLocals.ts(9,9): error TS2304: Cannot find name 'z'.
66
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorLocals.ts(14,9): error TS2304: Cannot find name 'z'.
77
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorLocals.ts(15,15): error TS2304: Cannot find name 'z'.
88
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorLocals.ts(16,14): error TS2339: Property 'z' does not exist on type 'D<T>'.
9-
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorLocals.ts(17,15): error TS2304: Cannot find name 'this'.
9+
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorLocals.ts(17,20): error TS2339: Property 'z' does not exist on type 'D<T>'.
1010
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorLocals.ts(19,9): error TS2304: Cannot find name 'z'.
1111

1212

@@ -24,8 +24,8 @@ tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencin
2424
~
2525
!!! error TS2339: Property 'z' does not exist on type 'C'.
2626
d: typeof this.z; // error
27-
~~~~
28-
!!! error TS2304: Cannot find name 'this'.
27+
~
28+
!!! error TS2339: Property 'z' does not exist on type 'C'.
2929
constructor(x) {
3030
z = 1;
3131
~
@@ -44,8 +44,8 @@ tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencin
4444
~
4545
!!! error TS2339: Property 'z' does not exist on type 'D<T>'.
4646
d: typeof this.z; // error
47-
~~~~
48-
!!! error TS2304: Cannot find name 'this'.
47+
~
48+
!!! error TS2339: Property 'z' does not exist on type 'D<T>'.
4949
constructor(x: T) {
5050
z = 1;
5151
~

tests/baselines/reference/initializerReferencingConstructorParameters.errors.txt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencin
22
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorParameters.ts(5,15): error TS2304: Cannot find name 'x'.
33
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorParameters.ts(10,9): error TS2663: Cannot find name 'x'. Did you mean the instance member 'this.x'?
44
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorParameters.ts(11,15): error TS2304: Cannot find name 'x'.
5-
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorParameters.ts(17,15): error TS2304: Cannot find name 'this'.
65
tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorParameters.ts(23,9): error TS2663: Cannot find name 'x'. Did you mean the instance member 'this.x'?
76

87

9-
==== tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorParameters.ts (6 errors) ====
8+
==== tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencingConstructorParameters.ts (5 errors) ====
109
// Initializer expressions for instance member variables are evaluated in the scope of the class constructor body but are not permitted to reference parameters or local variables of the constructor.
1110

1211
class C {
@@ -31,9 +30,7 @@ tests/cases/conformance/classes/propertyMemberDeclarations/initializerReferencin
3130

3231
class E {
3332
a = this.x; // ok
34-
b: typeof this.x; // error
35-
~~~~
36-
!!! error TS2304: Cannot find name 'this'.
33+
b: typeof this.x; // ok
3734
constructor(public x) { }
3835
}
3936

tests/baselines/reference/initializerReferencingConstructorParameters.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class D {
1515

1616
class E {
1717
a = this.x; // ok
18-
b: typeof this.x; // error
18+
b: typeof this.x; // ok
1919
constructor(public x) { }
2020
}
2121

tests/baselines/reference/initializerReferencingConstructorParameters.symbols

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ class E {
3636
>this : Symbol(E, Decl(initializerReferencingConstructorParameters.ts, 12, 1))
3737
>x : Symbol(E.x, Decl(initializerReferencingConstructorParameters.ts, 17, 16))
3838

39-
b: typeof this.x; // error
39+
b: typeof this.x; // ok
4040
>b : Symbol(E.b, Decl(initializerReferencingConstructorParameters.ts, 15, 15))
41+
>this.x : Symbol(E.x, Decl(initializerReferencingConstructorParameters.ts, 17, 16))
42+
>x : Symbol(E.x, Decl(initializerReferencingConstructorParameters.ts, 17, 16))
4143

4244
constructor(public x) { }
4345
>x : Symbol(E.x, Decl(initializerReferencingConstructorParameters.ts, 17, 16))

tests/baselines/reference/initializerReferencingConstructorParameters.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class E {
4040
>this : this
4141
>x : any
4242

43-
b: typeof this.x; // error
43+
b: typeof this.x; // ok
4444
>b : any
4545
>this.x : any
4646
>this : any
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
tests/cases/conformance/types/specifyingTypes/typeQueries/typeofThis.ts(24,19): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation.
2+
tests/cases/conformance/types/specifyingTypes/typeQueries/typeofThis.ts(32,19): error TS2532: Object is possibly 'undefined'.
3+
tests/cases/conformance/types/specifyingTypes/typeQueries/typeofThis.ts(46,23): error TS2331: 'this' cannot be referenced in a module or namespace body.
4+
tests/cases/conformance/types/specifyingTypes/typeQueries/typeofThis.ts(46,23): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation.
5+
tests/cases/conformance/types/specifyingTypes/typeQueries/typeofThis.ts(52,23): error TS2331: 'this' cannot be referenced in a module or namespace body.
6+
tests/cases/conformance/types/specifyingTypes/typeQueries/typeofThis.ts(52,23): error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation.
7+
tests/cases/conformance/types/specifyingTypes/typeQueries/typeofThis.ts(57,19): error TS7041: The containing arrow function captures the global value of 'this'.
8+
tests/cases/conformance/types/specifyingTypes/typeQueries/typeofThis.ts(57,24): error TS7017: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.
9+
10+
11+
==== tests/cases/conformance/types/specifyingTypes/typeQueries/typeofThis.ts (8 errors) ====
12+
class Test {
13+
data = {};
14+
constructor() {
15+
var copy: typeof this.data = {};
16+
}
17+
}
18+
19+
class Test1 {
20+
data = { foo: '' };
21+
['this'] = '';
22+
constructor() {
23+
var copy: typeof this.data = { foo: '' };
24+
var foo: typeof this.data.foo = '';
25+
26+
var self: typeof this = this;
27+
self.data;
28+
29+
var str: typeof this.this = '';
30+
}
31+
}
32+
33+
34+
function Test2() {
35+
let x: typeof this.no = 1;
36+
~~~~
37+
!!! error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation.
38+
}
39+
40+
function Test3(this: { no: number }) {
41+
let x: typeof this.no = 1;
42+
}
43+
44+
function Test4(this: { no: number } | undefined) {
45+
let x: typeof this.no = 1;
46+
~~~~
47+
!!! error TS2532: Object is possibly 'undefined'.
48+
}
49+
50+
class Test5 {
51+
no = 1;
52+
53+
f = () => {
54+
// should not capture this.
55+
let x: typeof this.no = 1;
56+
}
57+
}
58+
59+
namespace Test6 {
60+
export let f = () => {
61+
let x: typeof this.no = 1;
62+
~~~~
63+
!!! error TS2331: 'this' cannot be referenced in a module or namespace body.
64+
~~~~
65+
!!! error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation.
66+
}
67+
}
68+
69+
module Test7 {
70+
export let f = () => {
71+
let x: typeof this.no = 1;
72+
~~~~
73+
!!! error TS2331: 'this' cannot be referenced in a module or namespace body.
74+
~~~~
75+
!!! error TS2683: 'this' implicitly has type 'any' because it does not have a type annotation.
76+
}
77+
}
78+
79+
const Test8 = () => {
80+
let x: typeof this.no = 1;
81+
~~~~
82+
!!! error TS7041: The containing arrow function captures the global value of 'this'.
83+
~~
84+
!!! error TS7017: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.
85+
}
86+
87+
class Test9 {
88+
no = 0;
89+
this = 0;
90+
91+
f() {
92+
if (this instanceof Test9D1) {
93+
const d1: typeof this = this;
94+
d1.f1();
95+
}
96+
97+
if (this instanceof Test9D2) {
98+
const d2: typeof this = this;
99+
d2.f2();
100+
}
101+
}
102+
103+
g() {
104+
if (this.no === 1) {
105+
const no: typeof this.no = this.no;
106+
}
107+
108+
if (this.this === 1) {
109+
const no: typeof this.this = this.this;
110+
}
111+
}
112+
}
113+
114+
class Test9D1 {
115+
f1() {}
116+
}
117+
118+
class Test9D2 {
119+
f2() {}
120+
}
121+
122+
class Test10 {
123+
a?: { b?: string }
124+
125+
foo() {
126+
let a: typeof this.a = undefined as any;
127+
if (this.a) {
128+
let a: typeof this.a = undefined as any; // should narrow to { b?: string }
129+
let b: typeof this.a.b = undefined as any;
130+
131+
if (this.a.b) {
132+
let b: typeof this.a.b = undefined as any; // should narrow to string
133+
}
134+
}
135+
}
136+
}
137+
138+
class Test11 {
139+
this?: { x?: string };
140+
141+
foo() {
142+
const o = this;
143+
let bar: typeof o.this = {};
144+
145+
if (o.this && o.this.x) {
146+
let y: string = o.this.x; // should narrow to string
147+
}
148+
}
149+
}

0 commit comments

Comments
 (0)