Skip to content

Commit 9120121

Browse files
committed
Merge pull request #8356 from Microsoft/smarter-object-literal-this-contextual-type
`this` in object literals intersects contextual type and literal type
2 parents 4ebf488 + 9f7621c commit 9120121

File tree

5 files changed

+482
-43
lines changed

5 files changed

+482
-43
lines changed

src/compiler/checker.ts

Lines changed: 48 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5508,6 +5508,10 @@ namespace ts {
55085508
return !node.typeParameters && areAllParametersUntyped && !isNullaryArrow;
55095509
}
55105510

5511+
function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | MethodDeclaration {
5512+
return (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && isContextSensitiveFunctionLikeDeclaration(func);
5513+
}
5514+
55115515
function getTypeWithoutSignatures(type: Type): Type {
55125516
if (type.flags & TypeFlags.ObjectType) {
55135517
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
@@ -8179,6 +8183,21 @@ namespace ts {
81798183
captureLexicalThis(node, container);
81808184
}
81818185
if (isFunctionLike(container)) {
8186+
// If this is a function in a JS file, it might be a class method. Check if it's the RHS
8187+
// of a x.prototype.y = function [name]() { .... }
8188+
if (container.kind === SyntaxKind.FunctionExpression &&
8189+
isInJavaScriptFile(container.parent) &&
8190+
getSpecialPropertyAssignmentKind(container.parent) === SpecialPropertyAssignmentKind.PrototypeProperty) {
8191+
// Get the 'x' of 'x.prototype.y = f' (here, 'f' is 'container')
8192+
const className = (((container.parent as BinaryExpression) // x.prototype.y = f
8193+
.left as PropertyAccessExpression) // x.prototype.y
8194+
.expression as PropertyAccessExpression) // x.prototype
8195+
.expression; // x
8196+
const classSymbol = checkExpression(className).symbol;
8197+
if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) {
8198+
return getInferredClassType(classSymbol);
8199+
}
8200+
}
81828201
const type = getContextuallyTypedThisType(container);
81838202
if (type) {
81848203
return type;
@@ -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) {
@@ -8447,12 +8450,13 @@ namespace ts {
84478450
}
84488451

84498452
function getContextuallyTypedThisType(func: FunctionLikeDeclaration): Type {
8450-
if ((isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) &&
8451-
isContextSensitive(func) &&
8452-
func.kind !== SyntaxKind.ArrowFunction) {
8453+
if (isContextSensitiveFunctionOrObjectLiteralMethod(func) && func.kind !== SyntaxKind.ArrowFunction) {
84538454
const contextualSignature = getContextualSignature(func);
84548455
if (contextualSignature) {
8455-
return contextualSignature.thisType;
8456+
return contextualSignature.thisType || anyType;
8457+
}
8458+
else if (getContextualTypeForFunctionLikeDeclaration(func) === anyType) {
8459+
return anyType;
84568460
}
84578461
}
84588462

@@ -8462,34 +8466,31 @@ namespace ts {
84628466
// Return contextual type of parameter or undefined if no contextual type is available
84638467
function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type {
84648468
const func = parameter.parent;
8465-
if (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) {
8466-
if (isContextSensitive(func)) {
8467-
const contextualSignature = getContextualSignature(func);
8468-
if (contextualSignature) {
8469-
8470-
const funcHasRestParameters = hasRestParameter(func);
8471-
const len = func.parameters.length - (funcHasRestParameters ? 1 : 0);
8472-
const indexOfParameter = indexOf(func.parameters, parameter);
8473-
if (indexOfParameter < len) {
8474-
return getTypeAtPosition(contextualSignature, indexOfParameter);
8475-
}
8469+
if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) {
8470+
const contextualSignature = getContextualSignature(func);
8471+
if (contextualSignature) {
8472+
const funcHasRestParameters = hasRestParameter(func);
8473+
const len = func.parameters.length - (funcHasRestParameters ? 1 : 0);
8474+
const indexOfParameter = indexOf(func.parameters, parameter);
8475+
if (indexOfParameter < len) {
8476+
return getTypeAtPosition(contextualSignature, indexOfParameter);
8477+
}
84768478

8477-
// If last parameter is contextually rest parameter get its type
8478-
if (funcHasRestParameters &&
8479-
indexOfParameter === (func.parameters.length - 1) &&
8480-
isRestParameterIndex(contextualSignature, func.parameters.length - 1)) {
8481-
return getTypeOfSymbol(lastOrUndefined(contextualSignature.parameters));
8482-
}
8479+
// If last parameter is contextually rest parameter get its type
8480+
if (funcHasRestParameters &&
8481+
indexOfParameter === (func.parameters.length - 1) &&
8482+
isRestParameterIndex(contextualSignature, func.parameters.length - 1)) {
8483+
return getTypeOfSymbol(lastOrUndefined(contextualSignature.parameters));
84838484
}
84848485
}
84858486
}
84868487
return undefined;
84878488
}
84888489

84898490
// In a variable, parameter or property declaration with a type annotation,
8490-
// the contextual type of an initializer expression is the type of the variable, parameter or property.
8491-
// Otherwise, in a parameter declaration of a contextually typed function expression,
8492-
// the contextual type of an initializer expression is the contextual type of the parameter.
8491+
// the contextual type of an initializer expression is the type of the variable, parameter or property.
8492+
// Otherwise, in a parameter declaration of a contextually typed function expression,
8493+
// the contextual type of an initializer expression is the contextual type of the parameter.
84938494
// Otherwise, in a variable or parameter declaration with a binding pattern name,
84948495
// the contextual type of an initializer expression is the type implied by the binding pattern.
84958496
// Otherwise, in a binding pattern inside a variable or parameter declaration,
@@ -8843,16 +8844,20 @@ namespace ts {
88438844
: undefined;
88448845
}
88458846

8847+
function getContextualTypeForFunctionLikeDeclaration(node: FunctionExpression | MethodDeclaration) {
8848+
return isObjectLiteralMethod(node)
8849+
? getContextualTypeForObjectLiteralMethod(node)
8850+
: getApparentTypeOfContextualType(node);
8851+
}
8852+
88468853
// Return the contextual signature for a given expression node. A contextual type provides a
88478854
// contextual signature if it has a single call signature and if that call signature is non-generic.
88488855
// If the contextual type is a union type, get the signature from each type possible and if they are
88498856
// all identical ignoring their return type, the result is same signature but with return type as
88508857
// union type of return types from these signatures
88518858
function getContextualSignature(node: FunctionExpression | MethodDeclaration): Signature {
88528859
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
8853-
const type = isObjectLiteralMethod(node)
8854-
? getContextualTypeForObjectLiteralMethod(node)
8855-
: getApparentTypeOfContextualType(node);
8860+
const type = getContextualTypeForFunctionLikeDeclaration(node);
88568861
if (!type) {
88578862
return undefined;
88588863
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//// [thisTypeInFunctions2.ts]
2+
interface IndexedWithThis {
3+
// this is a workaround for React
4+
init?: (this: this) => void;
5+
willDestroy?: (this: any) => void;
6+
[propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any);
7+
}
8+
interface IndexedWithoutThis {
9+
// this is what React would like to write (and what they write today)
10+
init?: () => void;
11+
willDestroy?: () => void;
12+
[propName: string]: any;
13+
}
14+
interface SimpleInterface {
15+
foo(n: string);
16+
bar(): number;
17+
}
18+
declare function extend1(args: IndexedWithThis): void;
19+
declare function extend2(args: IndexedWithoutThis): void;
20+
declare function simple(arg: SimpleInterface): void;
21+
22+
extend1({
23+
init() {
24+
this // this: IndexedWithThis because of contextual typing.
25+
// this.mine
26+
this.willDestroy
27+
},
28+
mine: 12,
29+
foo() {
30+
this.url; // this: any because 'foo' matches the string indexer
31+
this.willDestroy;
32+
}
33+
});
34+
extend2({
35+
init() {
36+
this // this: any because the contextual signature of init doesn't specify this' type
37+
this.mine
38+
this.willDestroy
39+
},
40+
mine: 13,
41+
foo() {
42+
this // this: any because of the string indexer
43+
this.mine
44+
this.willDestroy
45+
}
46+
});
47+
48+
simple({
49+
foo(n) {
50+
return n.length + this.bar();
51+
},
52+
bar() {
53+
return 14;
54+
}
55+
})
56+
57+
58+
//// [thisTypeInFunctions2.js]
59+
extend1({
60+
init: function () {
61+
this; // this: IndexedWithThis because of contextual typing.
62+
// this.mine
63+
this.willDestroy;
64+
},
65+
mine: 12,
66+
foo: function () {
67+
this.url; // this: any because 'foo' matches the string indexer
68+
this.willDestroy;
69+
}
70+
});
71+
extend2({
72+
init: function () {
73+
this; // this: any because the contextual signature of init doesn't specify this' type
74+
this.mine;
75+
this.willDestroy;
76+
},
77+
mine: 13,
78+
foo: function () {
79+
this; // this: any because of the string indexer
80+
this.mine;
81+
this.willDestroy;
82+
}
83+
});
84+
simple({
85+
foo: function (n) {
86+
return n.length + this.bar();
87+
},
88+
bar: function () {
89+
return 14;
90+
}
91+
});
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
=== tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts ===
2+
interface IndexedWithThis {
3+
>IndexedWithThis : Symbol(IndexedWithThis, Decl(thisTypeInFunctions2.ts, 0, 0))
4+
5+
// this is a workaround for React
6+
init?: (this: this) => void;
7+
>init : Symbol(IndexedWithThis.init, Decl(thisTypeInFunctions2.ts, 0, 27))
8+
>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 2, 12))
9+
10+
willDestroy?: (this: any) => void;
11+
>willDestroy : Symbol(IndexedWithThis.willDestroy, Decl(thisTypeInFunctions2.ts, 2, 32))
12+
>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 3, 19))
13+
14+
[propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any);
15+
>propName : Symbol(propName, Decl(thisTypeInFunctions2.ts, 4, 5))
16+
>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 4, 87))
17+
>args : Symbol(args, Decl(thisTypeInFunctions2.ts, 4, 97))
18+
}
19+
interface IndexedWithoutThis {
20+
>IndexedWithoutThis : Symbol(IndexedWithoutThis, Decl(thisTypeInFunctions2.ts, 5, 1))
21+
22+
// this is what React would like to write (and what they write today)
23+
init?: () => void;
24+
>init : Symbol(IndexedWithoutThis.init, Decl(thisTypeInFunctions2.ts, 6, 30))
25+
26+
willDestroy?: () => void;
27+
>willDestroy : Symbol(IndexedWithoutThis.willDestroy, Decl(thisTypeInFunctions2.ts, 8, 22))
28+
29+
[propName: string]: any;
30+
>propName : Symbol(propName, Decl(thisTypeInFunctions2.ts, 10, 5))
31+
}
32+
interface SimpleInterface {
33+
>SimpleInterface : Symbol(SimpleInterface, Decl(thisTypeInFunctions2.ts, 11, 1))
34+
35+
foo(n: string);
36+
>foo : Symbol(SimpleInterface.foo, Decl(thisTypeInFunctions2.ts, 12, 27))
37+
>n : Symbol(n, Decl(thisTypeInFunctions2.ts, 13, 8))
38+
39+
bar(): number;
40+
>bar : Symbol(SimpleInterface.bar, Decl(thisTypeInFunctions2.ts, 13, 19))
41+
}
42+
declare function extend1(args: IndexedWithThis): void;
43+
>extend1 : Symbol(extend1, Decl(thisTypeInFunctions2.ts, 15, 1))
44+
>args : Symbol(args, Decl(thisTypeInFunctions2.ts, 16, 25))
45+
>IndexedWithThis : Symbol(IndexedWithThis, Decl(thisTypeInFunctions2.ts, 0, 0))
46+
47+
declare function extend2(args: IndexedWithoutThis): void;
48+
>extend2 : Symbol(extend2, Decl(thisTypeInFunctions2.ts, 16, 54))
49+
>args : Symbol(args, Decl(thisTypeInFunctions2.ts, 17, 25))
50+
>IndexedWithoutThis : Symbol(IndexedWithoutThis, Decl(thisTypeInFunctions2.ts, 5, 1))
51+
52+
declare function simple(arg: SimpleInterface): void;
53+
>simple : Symbol(simple, Decl(thisTypeInFunctions2.ts, 17, 57))
54+
>arg : Symbol(arg, Decl(thisTypeInFunctions2.ts, 18, 24))
55+
>SimpleInterface : Symbol(SimpleInterface, Decl(thisTypeInFunctions2.ts, 11, 1))
56+
57+
extend1({
58+
>extend1 : Symbol(extend1, Decl(thisTypeInFunctions2.ts, 15, 1))
59+
60+
init() {
61+
>init : Symbol(init, Decl(thisTypeInFunctions2.ts, 20, 9))
62+
63+
this // this: IndexedWithThis because of contextual typing.
64+
>this : Symbol(IndexedWithThis, Decl(thisTypeInFunctions2.ts, 0, 0))
65+
66+
// this.mine
67+
this.willDestroy
68+
>this.willDestroy : Symbol(IndexedWithThis.willDestroy, Decl(thisTypeInFunctions2.ts, 2, 32))
69+
>this : Symbol(IndexedWithThis, Decl(thisTypeInFunctions2.ts, 0, 0))
70+
>willDestroy : Symbol(IndexedWithThis.willDestroy, Decl(thisTypeInFunctions2.ts, 2, 32))
71+
72+
},
73+
mine: 12,
74+
>mine : Symbol(mine, Decl(thisTypeInFunctions2.ts, 25, 6))
75+
76+
foo() {
77+
>foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 26, 13))
78+
79+
this.url; // this: any because 'foo' matches the string indexer
80+
this.willDestroy;
81+
}
82+
});
83+
extend2({
84+
>extend2 : Symbol(extend2, Decl(thisTypeInFunctions2.ts, 16, 54))
85+
86+
init() {
87+
>init : Symbol(init, Decl(thisTypeInFunctions2.ts, 32, 9))
88+
89+
this // this: any because the contextual signature of init doesn't specify this' type
90+
this.mine
91+
this.willDestroy
92+
},
93+
mine: 13,
94+
>mine : Symbol(mine, Decl(thisTypeInFunctions2.ts, 37, 6))
95+
96+
foo() {
97+
>foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 38, 13))
98+
99+
this // this: any because of the string indexer
100+
this.mine
101+
this.willDestroy
102+
}
103+
});
104+
105+
simple({
106+
>simple : Symbol(simple, Decl(thisTypeInFunctions2.ts, 17, 57))
107+
108+
foo(n) {
109+
>foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 46, 8))
110+
>n : Symbol(n, Decl(thisTypeInFunctions2.ts, 47, 8))
111+
112+
return n.length + this.bar();
113+
>n.length : Symbol(String.length, Decl(lib.d.ts, --, --))
114+
>n : Symbol(n, Decl(thisTypeInFunctions2.ts, 47, 8))
115+
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
116+
117+
},
118+
bar() {
119+
>bar : Symbol(bar, Decl(thisTypeInFunctions2.ts, 49, 6))
120+
121+
return 14;
122+
}
123+
})
124+

0 commit comments

Comments
 (0)