Skip to content

Commit ea64fda

Browse files
committed
Improve detection of containing object literals to improve contextual this typing
1 parent 56a0825 commit ea64fda

File tree

6 files changed

+317
-11
lines changed

6 files changed

+317
-11
lines changed

src/compiler/checker.ts

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31184,12 +31184,51 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3118431184
}
3118531185
}
3118631186

31187+
function getContainingPropertyAssignment(node: Node): PropertyAssignment | undefined {
31188+
const parent = node.parent;
31189+
switch (parent.kind) {
31190+
case SyntaxKind.PropertyAssignment:
31191+
return parent as PropertyAssignment;
31192+
case SyntaxKind.ParenthesizedExpression:
31193+
case SyntaxKind.ConditionalExpression:
31194+
return getContainingPropertyAssignment(parent);
31195+
case SyntaxKind.BinaryExpression: {
31196+
const binaryExpression = parent as BinaryExpression;
31197+
switch (binaryExpression.operatorToken.kind) {
31198+
case SyntaxKind.AmpersandAmpersandToken:
31199+
case SyntaxKind.BarBarToken:
31200+
case SyntaxKind.QuestionQuestionToken:
31201+
return getContainingPropertyAssignment(parent);
31202+
case SyntaxKind.EqualsToken:
31203+
case SyntaxKind.AmpersandAmpersandEqualsToken:
31204+
case SyntaxKind.BarBarEqualsToken:
31205+
case SyntaxKind.QuestionQuestionEqualsToken:
31206+
case SyntaxKind.CommaToken:
31207+
if (node === binaryExpression.left) {
31208+
return;
31209+
}
31210+
return getContainingPropertyAssignment(parent);
31211+
}
31212+
}
31213+
}
31214+
}
31215+
3118731216
function getContainingObjectLiteral(func: SignatureDeclaration): ObjectLiteralExpression | undefined {
31188-
return (func.kind === SyntaxKind.MethodDeclaration ||
31189-
func.kind === SyntaxKind.GetAccessor ||
31190-
func.kind === SyntaxKind.SetAccessor) && func.parent.kind === SyntaxKind.ObjectLiteralExpression ? func.parent :
31191-
func.kind === SyntaxKind.FunctionExpression && func.parent.kind === SyntaxKind.PropertyAssignment ? func.parent.parent as ObjectLiteralExpression :
31192-
undefined;
31217+
switch (func.kind) {
31218+
case SyntaxKind.MethodDeclaration:
31219+
case SyntaxKind.GetAccessor:
31220+
case SyntaxKind.SetAccessor:
31221+
if (func.parent.kind !== SyntaxKind.ObjectLiteralExpression) {
31222+
return;
31223+
}
31224+
return func.parent;
31225+
case SyntaxKind.FunctionExpression:
31226+
const prop = getContainingPropertyAssignment(func);
31227+
if (!prop) {
31228+
return;
31229+
}
31230+
return prop.parent;
31231+
}
3119331232
}
3119431233

3119531234
function getThisTypeArgument(type: Type): Type | undefined {

tests/baselines/reference/thisInObjectJs.symbols

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,13 @@ let obj = {
3939
>f3 : Symbol(f3, Decl(index.js, 11, 4))
4040

4141
this.x = 1
42+
>this.x : Symbol(x, Decl(index.js, 1, 11))
43+
>this : Symbol(obj, Decl(index.js, 1, 9))
4244
>x : Symbol((Anonymous function).x, Decl(index.js, 12, 19))
4345

4446
this/*3*/
47+
>this : Symbol(obj, Decl(index.js, 1, 9))
48+
4549
}),
4650
}
4751

tests/baselines/reference/thisInObjectJs.types

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,18 @@ let obj = {
7575
this.x = 1
7676
>this.x = 1 : 1
7777
> : ^
78-
>this.x : any
79-
>this : any
80-
> : ^^^
81-
>x : any
82-
> : ^^^
78+
>this.x : number
79+
> : ^^^^^^
80+
>this : { x: number; y: number[]; fun: typeof fun; f2: () => void; f3: typeof (Anonymous function); }
81+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
82+
>x : number
83+
> : ^^^^^^
8384
>1 : 1
8485
> : ^
8586

8687
this/*3*/
87-
>this : any
88+
>this : { x: number; y: number[]; fun: typeof fun; f2: () => void; f3: typeof (Anonymous function); }
89+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
8890

8991
}),
9092
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//// [tests/cases/conformance/expressions/thisKeyword/thisInObjectLiterals2.ts] ////
2+
3+
=== thisInObjectLiterals2.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/54723
5+
6+
interface State {
7+
>State : Symbol(State, Decl(thisInObjectLiterals2.ts, 0, 0))
8+
9+
value: string;
10+
>value : Symbol(State.value, Decl(thisInObjectLiterals2.ts, 2, 17))
11+
12+
matches(value: string): boolean;
13+
>matches : Symbol(State.matches, Decl(thisInObjectLiterals2.ts, 3, 16))
14+
>value : Symbol(value, Decl(thisInObjectLiterals2.ts, 4, 10))
15+
}
16+
17+
declare function macthesState(state: { value: string }, value: string): boolean;
18+
>macthesState : Symbol(macthesState, Decl(thisInObjectLiterals2.ts, 5, 1))
19+
>state : Symbol(state, Decl(thisInObjectLiterals2.ts, 7, 30))
20+
>value : Symbol(value, Decl(thisInObjectLiterals2.ts, 7, 38))
21+
>value : Symbol(value, Decl(thisInObjectLiterals2.ts, 7, 55))
22+
23+
declare function isState(state: unknown): state is State;
24+
>isState : Symbol(isState, Decl(thisInObjectLiterals2.ts, 7, 80))
25+
>state : Symbol(state, Decl(thisInObjectLiterals2.ts, 8, 25))
26+
>state : Symbol(state, Decl(thisInObjectLiterals2.ts, 8, 25))
27+
>State : Symbol(State, Decl(thisInObjectLiterals2.ts, 0, 0))
28+
29+
function test(config: unknown, prevConfig: unknown) {
30+
>test : Symbol(test, Decl(thisInObjectLiterals2.ts, 8, 57))
31+
>config : Symbol(config, Decl(thisInObjectLiterals2.ts, 10, 14))
32+
>prevConfig : Symbol(prevConfig, Decl(thisInObjectLiterals2.ts, 10, 30))
33+
34+
if (isState(config)) {
35+
>isState : Symbol(isState, Decl(thisInObjectLiterals2.ts, 7, 80))
36+
>config : Symbol(config, Decl(thisInObjectLiterals2.ts, 10, 14))
37+
38+
return {
39+
...config,
40+
>config : Symbol(config, Decl(thisInObjectLiterals2.ts, 10, 14))
41+
42+
matches: isState(prevConfig)
43+
>matches : Symbol(matches, Decl(thisInObjectLiterals2.ts, 13, 16))
44+
>isState : Symbol(isState, Decl(thisInObjectLiterals2.ts, 7, 80))
45+
>prevConfig : Symbol(prevConfig, Decl(thisInObjectLiterals2.ts, 10, 30))
46+
47+
? prevConfig.matches
48+
>prevConfig.matches : Symbol(State.matches, Decl(thisInObjectLiterals2.ts, 3, 16))
49+
>prevConfig : Symbol(prevConfig, Decl(thisInObjectLiterals2.ts, 10, 30))
50+
>matches : Symbol(State.matches, Decl(thisInObjectLiterals2.ts, 3, 16))
51+
52+
: function (value: string) {
53+
>value : Symbol(value, Decl(thisInObjectLiterals2.ts, 16, 20))
54+
55+
return macthesState(this, value);
56+
>macthesState : Symbol(macthesState, Decl(thisInObjectLiterals2.ts, 5, 1))
57+
>this : Symbol(__object, Decl(thisInObjectLiterals2.ts, 12, 10))
58+
>value : Symbol(value, Decl(thisInObjectLiterals2.ts, 16, 20))
59+
60+
},
61+
};
62+
}
63+
64+
return config;
65+
>config : Symbol(config, Decl(thisInObjectLiterals2.ts, 10, 14))
66+
}
67+
68+
function test2(config: State) {
69+
>test2 : Symbol(test2, Decl(thisInObjectLiterals2.ts, 23, 1))
70+
>config : Symbol(config, Decl(thisInObjectLiterals2.ts, 25, 15))
71+
>State : Symbol(State, Decl(thisInObjectLiterals2.ts, 0, 0))
72+
73+
return {
74+
...config,
75+
>config : Symbol(config, Decl(thisInObjectLiterals2.ts, 25, 15))
76+
77+
matches: function (value: string) {
78+
>matches : Symbol(matches, Decl(thisInObjectLiterals2.ts, 27, 14))
79+
>value : Symbol(value, Decl(thisInObjectLiterals2.ts, 28, 23))
80+
81+
return macthesState(this, value);
82+
>macthesState : Symbol(macthesState, Decl(thisInObjectLiterals2.ts, 5, 1))
83+
>this : Symbol(__object, Decl(thisInObjectLiterals2.ts, 26, 8))
84+
>value : Symbol(value, Decl(thisInObjectLiterals2.ts, 28, 23))
85+
86+
},
87+
};
88+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//// [tests/cases/conformance/expressions/thisKeyword/thisInObjectLiterals2.ts] ////
2+
3+
=== thisInObjectLiterals2.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/54723
5+
6+
interface State {
7+
value: string;
8+
>value : string
9+
> : ^^^^^^
10+
11+
matches(value: string): boolean;
12+
>matches : (value: string) => boolean
13+
> : ^ ^^ ^^^^^
14+
>value : string
15+
> : ^^^^^^
16+
}
17+
18+
declare function macthesState(state: { value: string }, value: string): boolean;
19+
>macthesState : (state: { value: string; }, value: string) => boolean
20+
> : ^ ^^ ^^ ^^ ^^^^^
21+
>state : { value: string; }
22+
> : ^^^^^^^^^ ^^^
23+
>value : string
24+
> : ^^^^^^
25+
>value : string
26+
> : ^^^^^^
27+
28+
declare function isState(state: unknown): state is State;
29+
>isState : (state: unknown) => state is State
30+
> : ^ ^^ ^^^^^
31+
>state : unknown
32+
> : ^^^^^^^
33+
34+
function test(config: unknown, prevConfig: unknown) {
35+
>test : (config: unknown, prevConfig: unknown) => unknown
36+
> : ^ ^^ ^^ ^^ ^^^^^^^^^^^^
37+
>config : unknown
38+
> : ^^^^^^^
39+
>prevConfig : unknown
40+
> : ^^^^^^^
41+
42+
if (isState(config)) {
43+
>isState(config) : boolean
44+
> : ^^^^^^^
45+
>isState : (state: unknown) => state is State
46+
> : ^ ^^ ^^^^^
47+
>config : unknown
48+
> : ^^^^^^^
49+
50+
return {
51+
>{ ...config, matches: isState(prevConfig) ? prevConfig.matches : function (value: string) { return macthesState(this, value); }, } : { matches: (value: string) => boolean; value: string; }
52+
> : ^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^ ^^^
53+
54+
...config,
55+
>config : State
56+
> : ^^^^^
57+
58+
matches: isState(prevConfig)
59+
>matches : (value: string) => boolean
60+
> : ^ ^^ ^^^^^
61+
>isState(prevConfig) ? prevConfig.matches : function (value: string) { return macthesState(this, value); } : (value: string) => boolean
62+
> : ^ ^^ ^^^^^
63+
>isState(prevConfig) : boolean
64+
> : ^^^^^^^
65+
>isState : (state: unknown) => state is State
66+
> : ^ ^^ ^^^^^
67+
>prevConfig : unknown
68+
> : ^^^^^^^
69+
70+
? prevConfig.matches
71+
>prevConfig.matches : (value: string) => boolean
72+
> : ^ ^^ ^^^^^
73+
>prevConfig : State
74+
> : ^^^^^
75+
>matches : (value: string) => boolean
76+
> : ^ ^^ ^^^^^
77+
78+
: function (value: string) {
79+
>function (value: string) { return macthesState(this, value); } : (value: string) => boolean
80+
> : ^ ^^ ^^^^^^^^^^^^
81+
>value : string
82+
> : ^^^^^^
83+
84+
return macthesState(this, value);
85+
>macthesState(this, value) : boolean
86+
> : ^^^^^^^
87+
>macthesState : (state: { value: string; }, value: string) => boolean
88+
> : ^ ^^ ^^ ^^ ^^^^^
89+
>this : { matches: (value: string) => boolean; value: string; }
90+
> : ^^^^^^^^^^^^ ^^ ^^^^^ ^^^^^^^^^ ^^^
91+
>value : string
92+
> : ^^^^^^
93+
94+
},
95+
};
96+
}
97+
98+
return config;
99+
>config : unknown
100+
> : ^^^^^^^
101+
}
102+
103+
function test2(config: State) {
104+
>test2 : (config: State) => { matches: (value: string) => boolean; value: string; }
105+
> : ^ ^^ ^^^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^
106+
>config : State
107+
> : ^^^^^
108+
109+
return {
110+
>{ ...config, matches: function (value: string) { return macthesState(this, value); }, } : { matches: (value: string) => boolean; value: string; }
111+
> : ^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^
112+
113+
...config,
114+
>config : State
115+
> : ^^^^^
116+
117+
matches: function (value: string) {
118+
>matches : (value: string) => boolean
119+
> : ^ ^^ ^^^^^^^^^^^^
120+
>function (value: string) { return macthesState(this, value); } : (value: string) => boolean
121+
> : ^ ^^ ^^^^^^^^^^^^
122+
>value : string
123+
> : ^^^^^^
124+
125+
return macthesState(this, value);
126+
>macthesState(this, value) : boolean
127+
> : ^^^^^^^
128+
>macthesState : (state: { value: string; }, value: string) => boolean
129+
> : ^ ^^ ^^ ^^ ^^^^^
130+
>this : { matches: (value: string) => boolean; value: string; }
131+
> : ^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^ ^^^
132+
>value : string
133+
> : ^^^^^^
134+
135+
},
136+
};
137+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
// https://github.com/microsoft/TypeScript/issues/54723
5+
6+
interface State {
7+
value: string;
8+
matches(value: string): boolean;
9+
}
10+
11+
declare function macthesState(state: { value: string }, value: string): boolean;
12+
declare function isState(state: unknown): state is State;
13+
14+
function test(config: unknown, prevConfig: unknown) {
15+
if (isState(config)) {
16+
return {
17+
...config,
18+
matches: isState(prevConfig)
19+
? prevConfig.matches
20+
: function (value: string) {
21+
return macthesState(this, value);
22+
},
23+
};
24+
}
25+
26+
return config;
27+
}
28+
29+
function test2(config: State) {
30+
return {
31+
...config,
32+
matches: function (value: string) {
33+
return macthesState(this, value);
34+
},
35+
};
36+
}

0 commit comments

Comments
 (0)