Merge pull request #8356 from Microsoft/smarter-object-literal-this-contextual-type

`this` in object literals intersects contextual type and literal type
This commit is contained in:
Nathan Shively-Sanders
2016-04-28 15:27:25 -07:00
5 changed files with 482 additions and 43 deletions
+48 -43
View File
@@ -5508,6 +5508,10 @@ namespace ts {
return !node.typeParameters && areAllParametersUntyped && !isNullaryArrow;
}
function isContextSensitiveFunctionOrObjectLiteralMethod(func: Node): func is FunctionExpression | MethodDeclaration {
return (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) && isContextSensitiveFunctionLikeDeclaration(func);
}
function getTypeWithoutSignatures(type: Type): Type {
if (type.flags & TypeFlags.ObjectType) {
const resolved = resolveStructuredTypeMembers(<ObjectType>type);
@@ -8179,6 +8183,21 @@ namespace ts {
captureLexicalThis(node, container);
}
if (isFunctionLike(container)) {
// If this is a function in a JS file, it might be a class method. Check if it's the RHS
// of a x.prototype.y = function [name]() { .... }
if (container.kind === SyntaxKind.FunctionExpression &&
isInJavaScriptFile(container.parent) &&
getSpecialPropertyAssignmentKind(container.parent) === SpecialPropertyAssignmentKind.PrototypeProperty) {
// Get the 'x' of 'x.prototype.y = f' (here, 'f' is 'container')
const className = (((container.parent as BinaryExpression) // x.prototype.y = f
.left as PropertyAccessExpression) // x.prototype.y
.expression as PropertyAccessExpression) // x.prototype
.expression; // x
const classSymbol = checkExpression(className).symbol;
if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) {
return getInferredClassType(classSymbol);
}
}
const type = getContextuallyTypedThisType(container);
if (type) {
return type;
@@ -8207,22 +8226,6 @@ namespace ts {
if (type && type !== unknownType) {
return type;
}
// If this is a function in a JS file, it might be a class method. Check if it's the RHS
// of a x.prototype.y = function [name]() { .... }
if (container.kind === SyntaxKind.FunctionExpression) {
if (getSpecialPropertyAssignmentKind(container.parent) === SpecialPropertyAssignmentKind.PrototypeProperty) {
// Get the 'x' of 'x.prototype.y = f' (here, 'f' is 'container')
const className = (((container.parent as BinaryExpression) // x.prototype.y = f
.left as PropertyAccessExpression) // x.prototype.y
.expression as PropertyAccessExpression) // x.prototype
.expression; // x
const classSymbol = checkExpression(className).symbol;
if (classSymbol && classSymbol.members && (classSymbol.flags & SymbolFlags.Function)) {
return getInferredClassType(classSymbol);
}
}
}
}
if (compilerOptions.noImplicitThis) {
@@ -8447,12 +8450,13 @@ namespace ts {
}
function getContextuallyTypedThisType(func: FunctionLikeDeclaration): Type {
if ((isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) &&
isContextSensitive(func) &&
func.kind !== SyntaxKind.ArrowFunction) {
if (isContextSensitiveFunctionOrObjectLiteralMethod(func) && func.kind !== SyntaxKind.ArrowFunction) {
const contextualSignature = getContextualSignature(func);
if (contextualSignature) {
return contextualSignature.thisType;
return contextualSignature.thisType || anyType;
}
else if (getContextualTypeForFunctionLikeDeclaration(func) === anyType) {
return anyType;
}
}
@@ -8462,24 +8466,21 @@ namespace ts {
// Return contextual type of parameter or undefined if no contextual type is available
function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type {
const func = parameter.parent;
if (isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) {
if (isContextSensitive(func)) {
const contextualSignature = getContextualSignature(func);
if (contextualSignature) {
if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) {
const contextualSignature = getContextualSignature(func);
if (contextualSignature) {
const funcHasRestParameters = hasRestParameter(func);
const len = func.parameters.length - (funcHasRestParameters ? 1 : 0);
const indexOfParameter = indexOf(func.parameters, parameter);
if (indexOfParameter < len) {
return getTypeAtPosition(contextualSignature, indexOfParameter);
}
const funcHasRestParameters = hasRestParameter(func);
const len = func.parameters.length - (funcHasRestParameters ? 1 : 0);
const indexOfParameter = indexOf(func.parameters, parameter);
if (indexOfParameter < len) {
return getTypeAtPosition(contextualSignature, indexOfParameter);
}
// If last parameter is contextually rest parameter get its type
if (funcHasRestParameters &&
indexOfParameter === (func.parameters.length - 1) &&
isRestParameterIndex(contextualSignature, func.parameters.length - 1)) {
return getTypeOfSymbol(lastOrUndefined(contextualSignature.parameters));
}
// If last parameter is contextually rest parameter get its type
if (funcHasRestParameters &&
indexOfParameter === (func.parameters.length - 1) &&
isRestParameterIndex(contextualSignature, func.parameters.length - 1)) {
return getTypeOfSymbol(lastOrUndefined(contextualSignature.parameters));
}
}
}
@@ -8487,9 +8488,9 @@ namespace ts {
}
// In a variable, parameter or property declaration with a type annotation,
// the contextual type of an initializer expression is the type of the variable, parameter or property.
// Otherwise, in a parameter declaration of a contextually typed function expression,
// the contextual type of an initializer expression is the contextual type of the parameter.
// the contextual type of an initializer expression is the type of the variable, parameter or property.
// Otherwise, in a parameter declaration of a contextually typed function expression,
// the contextual type of an initializer expression is the contextual type of the parameter.
// Otherwise, in a variable or parameter declaration with a binding pattern name,
// the contextual type of an initializer expression is the type implied by the binding pattern.
// Otherwise, in a binding pattern inside a variable or parameter declaration,
@@ -8843,6 +8844,12 @@ namespace ts {
: undefined;
}
function getContextualTypeForFunctionLikeDeclaration(node: FunctionExpression | MethodDeclaration) {
return isObjectLiteralMethod(node)
? getContextualTypeForObjectLiteralMethod(node)
: getApparentTypeOfContextualType(node);
}
// Return the contextual signature for a given expression node. A contextual type provides a
// contextual signature if it has a single call signature and if that call signature is non-generic.
// If the contextual type is a union type, get the signature from each type possible and if they are
@@ -8850,9 +8857,7 @@ namespace ts {
// union type of return types from these signatures
function getContextualSignature(node: FunctionExpression | MethodDeclaration): Signature {
Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node));
const type = isObjectLiteralMethod(node)
? getContextualTypeForObjectLiteralMethod(node)
: getApparentTypeOfContextualType(node);
const type = getContextualTypeForFunctionLikeDeclaration(node);
if (!type) {
return undefined;
}
@@ -0,0 +1,91 @@
//// [thisTypeInFunctions2.ts]
interface IndexedWithThis {
// this is a workaround for React
init?: (this: this) => void;
willDestroy?: (this: any) => void;
[propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any);
}
interface IndexedWithoutThis {
// this is what React would like to write (and what they write today)
init?: () => void;
willDestroy?: () => void;
[propName: string]: any;
}
interface SimpleInterface {
foo(n: string);
bar(): number;
}
declare function extend1(args: IndexedWithThis): void;
declare function extend2(args: IndexedWithoutThis): void;
declare function simple(arg: SimpleInterface): void;
extend1({
init() {
this // this: IndexedWithThis because of contextual typing.
// this.mine
this.willDestroy
},
mine: 12,
foo() {
this.url; // this: any because 'foo' matches the string indexer
this.willDestroy;
}
});
extend2({
init() {
this // this: any because the contextual signature of init doesn't specify this' type
this.mine
this.willDestroy
},
mine: 13,
foo() {
this // this: any because of the string indexer
this.mine
this.willDestroy
}
});
simple({
foo(n) {
return n.length + this.bar();
},
bar() {
return 14;
}
})
//// [thisTypeInFunctions2.js]
extend1({
init: function () {
this; // this: IndexedWithThis because of contextual typing.
// this.mine
this.willDestroy;
},
mine: 12,
foo: function () {
this.url; // this: any because 'foo' matches the string indexer
this.willDestroy;
}
});
extend2({
init: function () {
this; // this: any because the contextual signature of init doesn't specify this' type
this.mine;
this.willDestroy;
},
mine: 13,
foo: function () {
this; // this: any because of the string indexer
this.mine;
this.willDestroy;
}
});
simple({
foo: function (n) {
return n.length + this.bar();
},
bar: function () {
return 14;
}
});
@@ -0,0 +1,124 @@
=== tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts ===
interface IndexedWithThis {
>IndexedWithThis : Symbol(IndexedWithThis, Decl(thisTypeInFunctions2.ts, 0, 0))
// this is a workaround for React
init?: (this: this) => void;
>init : Symbol(IndexedWithThis.init, Decl(thisTypeInFunctions2.ts, 0, 27))
>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 2, 12))
willDestroy?: (this: any) => void;
>willDestroy : Symbol(IndexedWithThis.willDestroy, Decl(thisTypeInFunctions2.ts, 2, 32))
>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 3, 19))
[propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any);
>propName : Symbol(propName, Decl(thisTypeInFunctions2.ts, 4, 5))
>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 4, 87))
>args : Symbol(args, Decl(thisTypeInFunctions2.ts, 4, 97))
}
interface IndexedWithoutThis {
>IndexedWithoutThis : Symbol(IndexedWithoutThis, Decl(thisTypeInFunctions2.ts, 5, 1))
// this is what React would like to write (and what they write today)
init?: () => void;
>init : Symbol(IndexedWithoutThis.init, Decl(thisTypeInFunctions2.ts, 6, 30))
willDestroy?: () => void;
>willDestroy : Symbol(IndexedWithoutThis.willDestroy, Decl(thisTypeInFunctions2.ts, 8, 22))
[propName: string]: any;
>propName : Symbol(propName, Decl(thisTypeInFunctions2.ts, 10, 5))
}
interface SimpleInterface {
>SimpleInterface : Symbol(SimpleInterface, Decl(thisTypeInFunctions2.ts, 11, 1))
foo(n: string);
>foo : Symbol(SimpleInterface.foo, Decl(thisTypeInFunctions2.ts, 12, 27))
>n : Symbol(n, Decl(thisTypeInFunctions2.ts, 13, 8))
bar(): number;
>bar : Symbol(SimpleInterface.bar, Decl(thisTypeInFunctions2.ts, 13, 19))
}
declare function extend1(args: IndexedWithThis): void;
>extend1 : Symbol(extend1, Decl(thisTypeInFunctions2.ts, 15, 1))
>args : Symbol(args, Decl(thisTypeInFunctions2.ts, 16, 25))
>IndexedWithThis : Symbol(IndexedWithThis, Decl(thisTypeInFunctions2.ts, 0, 0))
declare function extend2(args: IndexedWithoutThis): void;
>extend2 : Symbol(extend2, Decl(thisTypeInFunctions2.ts, 16, 54))
>args : Symbol(args, Decl(thisTypeInFunctions2.ts, 17, 25))
>IndexedWithoutThis : Symbol(IndexedWithoutThis, Decl(thisTypeInFunctions2.ts, 5, 1))
declare function simple(arg: SimpleInterface): void;
>simple : Symbol(simple, Decl(thisTypeInFunctions2.ts, 17, 57))
>arg : Symbol(arg, Decl(thisTypeInFunctions2.ts, 18, 24))
>SimpleInterface : Symbol(SimpleInterface, Decl(thisTypeInFunctions2.ts, 11, 1))
extend1({
>extend1 : Symbol(extend1, Decl(thisTypeInFunctions2.ts, 15, 1))
init() {
>init : Symbol(init, Decl(thisTypeInFunctions2.ts, 20, 9))
this // this: IndexedWithThis because of contextual typing.
>this : Symbol(IndexedWithThis, Decl(thisTypeInFunctions2.ts, 0, 0))
// this.mine
this.willDestroy
>this.willDestroy : Symbol(IndexedWithThis.willDestroy, Decl(thisTypeInFunctions2.ts, 2, 32))
>this : Symbol(IndexedWithThis, Decl(thisTypeInFunctions2.ts, 0, 0))
>willDestroy : Symbol(IndexedWithThis.willDestroy, Decl(thisTypeInFunctions2.ts, 2, 32))
},
mine: 12,
>mine : Symbol(mine, Decl(thisTypeInFunctions2.ts, 25, 6))
foo() {
>foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 26, 13))
this.url; // this: any because 'foo' matches the string indexer
this.willDestroy;
}
});
extend2({
>extend2 : Symbol(extend2, Decl(thisTypeInFunctions2.ts, 16, 54))
init() {
>init : Symbol(init, Decl(thisTypeInFunctions2.ts, 32, 9))
this // this: any because the contextual signature of init doesn't specify this' type
this.mine
this.willDestroy
},
mine: 13,
>mine : Symbol(mine, Decl(thisTypeInFunctions2.ts, 37, 6))
foo() {
>foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 38, 13))
this // this: any because of the string indexer
this.mine
this.willDestroy
}
});
simple({
>simple : Symbol(simple, Decl(thisTypeInFunctions2.ts, 17, 57))
foo(n) {
>foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 46, 8))
>n : Symbol(n, Decl(thisTypeInFunctions2.ts, 47, 8))
return n.length + this.bar();
>n.length : Symbol(String.length, Decl(lib.d.ts, --, --))
>n : Symbol(n, Decl(thisTypeInFunctions2.ts, 47, 8))
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
},
bar() {
>bar : Symbol(bar, Decl(thisTypeInFunctions2.ts, 49, 6))
return 14;
}
})
@@ -0,0 +1,165 @@
=== tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts ===
interface IndexedWithThis {
>IndexedWithThis : IndexedWithThis
// this is a workaround for React
init?: (this: this) => void;
>init : (this: this) => void
>this : this
willDestroy?: (this: any) => void;
>willDestroy : (this: any) => void
>this : any
[propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any);
>propName : string
>null : null
>this : any
>args : any[]
}
interface IndexedWithoutThis {
>IndexedWithoutThis : IndexedWithoutThis
// this is what React would like to write (and what they write today)
init?: () => void;
>init : () => void
willDestroy?: () => void;
>willDestroy : () => void
[propName: string]: any;
>propName : string
}
interface SimpleInterface {
>SimpleInterface : SimpleInterface
foo(n: string);
>foo : (n: string) => any
>n : string
bar(): number;
>bar : () => number
}
declare function extend1(args: IndexedWithThis): void;
>extend1 : (args: IndexedWithThis) => void
>args : IndexedWithThis
>IndexedWithThis : IndexedWithThis
declare function extend2(args: IndexedWithoutThis): void;
>extend2 : (args: IndexedWithoutThis) => void
>args : IndexedWithoutThis
>IndexedWithoutThis : IndexedWithoutThis
declare function simple(arg: SimpleInterface): void;
>simple : (arg: SimpleInterface) => void
>arg : SimpleInterface
>SimpleInterface : SimpleInterface
extend1({
>extend1({ init() { this // this: IndexedWithThis because of contextual typing. // this.mine this.willDestroy }, mine: 12, foo() { this.url; // this: any because 'foo' matches the string indexer this.willDestroy; }}) : void
>extend1 : (args: IndexedWithThis) => void
>{ init() { this // this: IndexedWithThis because of contextual typing. // this.mine this.willDestroy }, mine: 12, foo() { this.url; // this: any because 'foo' matches the string indexer this.willDestroy; }} : { init(): void; mine: number; foo(): void; }
init() {
>init : () => void
this // this: IndexedWithThis because of contextual typing.
>this : IndexedWithThis
// this.mine
this.willDestroy
>this.willDestroy : (this: any) => void
>this : IndexedWithThis
>willDestroy : (this: any) => void
},
mine: 12,
>mine : number
>12 : number
foo() {
>foo : () => void
this.url; // this: any because 'foo' matches the string indexer
>this.url : any
>this : any
>url : any
this.willDestroy;
>this.willDestroy : any
>this : any
>willDestroy : any
}
});
extend2({
>extend2({ init() { this // this: any because the contextual signature of init doesn't specify this' type this.mine this.willDestroy }, mine: 13, foo() { this // this: any because of the string indexer this.mine this.willDestroy }}) : void
>extend2 : (args: IndexedWithoutThis) => void
>{ init() { this // this: any because the contextual signature of init doesn't specify this' type this.mine this.willDestroy }, mine: 13, foo() { this // this: any because of the string indexer this.mine this.willDestroy }} : { init(): void; mine: number; foo(): void; }
init() {
>init : () => void
this // this: any because the contextual signature of init doesn't specify this' type
>this : any
this.mine
>this.mine : any
>this : any
>mine : any
this.willDestroy
>this.willDestroy : any
>this : any
>willDestroy : any
},
mine: 13,
>mine : number
>13 : number
foo() {
>foo : () => void
this // this: any because of the string indexer
>this : any
this.mine
>this.mine : any
>this : any
>mine : any
this.willDestroy
>this.willDestroy : any
>this : any
>willDestroy : any
}
});
simple({
>simple({ foo(n) { return n.length + this.bar(); }, bar() { return 14; }}) : void
>simple : (arg: SimpleInterface) => void
>{ foo(n) { return n.length + this.bar(); }, bar() { return 14; }} : { foo(n: string): any; bar(): number; }
foo(n) {
>foo : (n: string) => any
>n : string
return n.length + this.bar();
>n.length + this.bar() : any
>n.length : number
>n : string
>length : number
>this.bar() : any
>this.bar : any
>this : any
>bar : any
},
bar() {
>bar : () => number
return 14;
>14 : number
}
})
@@ -0,0 +1,54 @@
interface IndexedWithThis {
// this is a workaround for React
init?: (this: this) => void;
willDestroy?: (this: any) => void;
[propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any);
}
interface IndexedWithoutThis {
// this is what React would like to write (and what they write today)
init?: () => void;
willDestroy?: () => void;
[propName: string]: any;
}
interface SimpleInterface {
foo(n: string);
bar(): number;
}
declare function extend1(args: IndexedWithThis): void;
declare function extend2(args: IndexedWithoutThis): void;
declare function simple(arg: SimpleInterface): void;
extend1({
init() {
this // this: IndexedWithThis because of contextual typing.
// this.mine
this.willDestroy
},
mine: 12,
foo() {
this.url; // this: any because 'foo' matches the string indexer
this.willDestroy;
}
});
extend2({
init() {
this // this: any because the contextual signature of init doesn't specify this' type
this.mine
this.willDestroy
},
mine: 13,
foo() {
this // this: any because of the string indexer
this.mine
this.willDestroy
}
});
simple({
foo(n) {
return n.length + this.bar();
},
bar() {
return 14;
}
})