Skip to content

Commit 3a74f48

Browse files
committed
this in object literals intersects contextual type and literal type
1 parent 4ebf488 commit 3a74f48

5 files changed

Lines changed: 248 additions & 19 deletions

File tree

src/compiler/checker.ts

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8179,6 +8179,21 @@ namespace ts {
81798179
captureLexicalThis(node, container);
81808180
}
81818181
if (isFunctionLike(container)) {
8182+
// If this is a function in a JS file, it might be a class method. Check if it's the RHS
8183+
// of a x.prototype.y = function [name]() { .... }
8184+
if (container.kind === SyntaxKind.FunctionExpression &&
8185+
isInJavaScriptFile(container.parent) &&
8186+
getSpecialPropertyAssignmentKind(container.parent) === SpecialPropertyAssignmentKind.PrototypeProperty) {
8187+
// Get the 'x' of 'x.prototype.y = f' (here, 'f' is 'container')
8188+
const className = (((container.parent as BinaryExpression) // x.prototype.y = f
8189+
.left as PropertyAccessExpression) // x.prototype.y
8190+
.expression as PropertyAccessExpression) // x.prototype
8191+
.expression; // x
8192+
const classSymbol = checkExpression(className).symbol;
8193+
if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) {
8194+
return getInferredClassType(classSymbol);
8195+
}
8196+
}
81828197
const type = getContextuallyTypedThisType(container);
81838198
if (type) {
81848199
return type;
@@ -8190,9 +8205,13 @@ namespace ts {
81908205
if (container.parent && container.parent.kind === SyntaxKind.ObjectLiteralExpression) {
81918206
// Note: this works because object literal methods are deferred,
81928207
// which means that the type of the containing object literal is already known.
8193-
const type = checkExpressionCached(<ObjectLiteralExpression>container.parent);
8194-
if (type) {
8195-
return type;
8208+
const contextualType = getContextualType(container.parent as ObjectLiteralExpression);
8209+
const literalType = checkExpressionCached(<ObjectLiteralExpression>container.parent);
8210+
if (contextualType && literalType) {
8211+
return getIntersectionType([contextualType, literalType]);
8212+
}
8213+
else if (contextualType || literalType) {
8214+
return contextualType || literalType;
81968215
}
81978216
}
81988217
}
@@ -8207,22 +8226,6 @@ namespace ts {
82078226
if (type && type !== unknownType) {
82088227
return type;
82098228
}
8210-
8211-
// If this is a function in a JS file, it might be a class method. Check if it's the RHS
8212-
// of a x.prototype.y = function [name]() { .... }
8213-
if (container.kind === SyntaxKind.FunctionExpression) {
8214-
if (getSpecialPropertyAssignmentKind(container.parent) === SpecialPropertyAssignmentKind.PrototypeProperty) {
8215-
// Get the 'x' of 'x.prototype.y = f' (here, 'f' is 'container')
8216-
const className = (((container.parent as BinaryExpression) // x.prototype.y = f
8217-
.left as PropertyAccessExpression) // x.prototype.y
8218-
.expression as PropertyAccessExpression) // x.prototype
8219-
.expression; // x
8220-
const classSymbol = checkExpression(className).symbol;
8221-
if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) {
8222-
return getInferredClassType(classSymbol);
8223-
}
8224-
}
8225-
}
82268229
}
82278230

82288231
if (compilerOptions.noImplicitThis) {
@@ -8450,6 +8453,12 @@ namespace ts {
84508453
if ((isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) &&
84518454
isContextSensitive(func) &&
84528455
func.kind !== SyntaxKind.ArrowFunction) {
8456+
const type = isObjectLiteralMethod(func)
8457+
? getContextualTypeForObjectLiteralMethod(func)
8458+
: getApparentTypeOfContextualType(func);
8459+
if (type === anyType) {
8460+
return anyType;
8461+
}
84538462
const contextualSignature = getContextualSignature(func);
84548463
if (contextualSignature) {
84558464
return contextualSignature.thisType;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//// [thisTypeInFunctions2.ts]
2+
interface Arguments {
3+
init?: (this: void) => void;
4+
willDestroy?: (this: any) => void;
5+
[propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any);
6+
}
7+
declare function extend(arguments: Arguments): void;
8+
class Mixin {
9+
stuff: number;
10+
}
11+
12+
extend({
13+
init() {
14+
this
15+
},
16+
mine: 12,
17+
bar() {
18+
this.init();
19+
},
20+
foo() {
21+
this.bar;
22+
this.url
23+
this.handler()
24+
this.baz
25+
this.willDestroy
26+
}
27+
})
28+
29+
30+
//// [thisTypeInFunctions2.js]
31+
var Mixin = (function () {
32+
function Mixin() {
33+
}
34+
return Mixin;
35+
}());
36+
extend({
37+
init: function () {
38+
this;
39+
},
40+
mine: 12,
41+
bar: function () {
42+
this.init();
43+
},
44+
foo: function () {
45+
this.bar;
46+
this.url;
47+
this.handler();
48+
this.baz;
49+
this.willDestroy;
50+
}
51+
});
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
=== tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts ===
2+
interface Arguments {
3+
>Arguments : Symbol(Arguments, Decl(thisTypeInFunctions2.ts, 0, 0))
4+
5+
init?: (this: void) => void;
6+
>init : Symbol(Arguments.init, Decl(thisTypeInFunctions2.ts, 0, 21))
7+
>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 1, 12))
8+
9+
willDestroy?: (this: any) => void;
10+
>willDestroy : Symbol(Arguments.willDestroy, Decl(thisTypeInFunctions2.ts, 1, 32))
11+
>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 2, 19))
12+
13+
[propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any);
14+
>propName : Symbol(propName, Decl(thisTypeInFunctions2.ts, 3, 5))
15+
>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 3, 87))
16+
>args : Symbol(args, Decl(thisTypeInFunctions2.ts, 3, 97))
17+
}
18+
declare function extend(arguments: Arguments): void;
19+
>extend : Symbol(extend, Decl(thisTypeInFunctions2.ts, 4, 1))
20+
>arguments : Symbol(arguments, Decl(thisTypeInFunctions2.ts, 5, 24))
21+
>Arguments : Symbol(Arguments, Decl(thisTypeInFunctions2.ts, 0, 0))
22+
23+
class Mixin {
24+
>Mixin : Symbol(Mixin, Decl(thisTypeInFunctions2.ts, 5, 52))
25+
26+
stuff: number;
27+
>stuff : Symbol(Mixin.stuff, Decl(thisTypeInFunctions2.ts, 6, 13))
28+
}
29+
30+
extend({
31+
>extend : Symbol(extend, Decl(thisTypeInFunctions2.ts, 4, 1))
32+
33+
init() {
34+
>init : Symbol(init, Decl(thisTypeInFunctions2.ts, 10, 8))
35+
36+
this
37+
},
38+
mine: 12,
39+
>mine : Symbol(mine, Decl(thisTypeInFunctions2.ts, 13, 6))
40+
41+
bar() {
42+
>bar : Symbol(bar, Decl(thisTypeInFunctions2.ts, 14, 13))
43+
44+
this.init();
45+
},
46+
foo() {
47+
>foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 17, 6))
48+
49+
this.bar;
50+
this.url
51+
this.handler()
52+
this.baz
53+
this.willDestroy
54+
}
55+
})
56+
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
=== tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts ===
2+
interface Arguments {
3+
>Arguments : Arguments
4+
5+
init?: (this: void) => void;
6+
>init : (this: void) => void
7+
>this : void
8+
9+
willDestroy?: (this: any) => void;
10+
>willDestroy : (this: any) => void
11+
>this : any
12+
13+
[propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any);
14+
>propName : string
15+
>null : null
16+
>this : any
17+
>args : any[]
18+
}
19+
declare function extend(arguments: Arguments): void;
20+
>extend : (arguments: Arguments) => void
21+
>arguments : Arguments
22+
>Arguments : Arguments
23+
24+
class Mixin {
25+
>Mixin : Mixin
26+
27+
stuff: number;
28+
>stuff : number
29+
}
30+
31+
extend({
32+
>extend({ init() { this }, mine: 12, bar() { this.init(); }, foo() { this.bar; this.url this.handler() this.baz this.willDestroy }}) : void
33+
>extend : (arguments: Arguments) => void
34+
>{ init() { this }, mine: 12, bar() { this.init(); }, foo() { this.bar; this.url this.handler() this.baz this.willDestroy }} : { init(): void; mine: number; bar(): void; foo(): void; }
35+
36+
init() {
37+
>init : () => void
38+
39+
this
40+
>this : void
41+
42+
},
43+
mine: 12,
44+
>mine : number
45+
>12 : number
46+
47+
bar() {
48+
>bar : () => void
49+
50+
this.init();
51+
>this.init() : any
52+
>this.init : any
53+
>this : any
54+
>init : any
55+
56+
},
57+
foo() {
58+
>foo : () => void
59+
60+
this.bar;
61+
>this.bar : any
62+
>this : any
63+
>bar : any
64+
65+
this.url
66+
>this.url : any
67+
>this : any
68+
>url : any
69+
70+
this.handler()
71+
>this.handler() : any
72+
>this.handler : any
73+
>this : any
74+
>handler : any
75+
76+
this.baz
77+
>this.baz : any
78+
>this : any
79+
>baz : any
80+
81+
this.willDestroy
82+
>this.willDestroy : any
83+
>this : any
84+
>willDestroy : any
85+
}
86+
})
87+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
interface Arguments {
2+
init?: (this: void) => void;
3+
willDestroy?: (this: any) => void;
4+
[propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any);
5+
}
6+
declare function extend(arguments: Arguments): void;
7+
class Mixin {
8+
stuff: number;
9+
}
10+
11+
extend({
12+
init() {
13+
this
14+
},
15+
mine: 12,
16+
bar() {
17+
this.init();
18+
},
19+
foo() {
20+
this.bar;
21+
this.url
22+
this.handler()
23+
this.baz
24+
this.willDestroy
25+
}
26+
})

0 commit comments

Comments
 (0)