this in object literals intersects contextual type and literal type

This commit is contained in:
Nathan Shively-Sanders
2016-04-28 10:09:04 -07:00
parent 4ebf4880e5
commit 3a74f483ba
5 changed files with 248 additions and 19 deletions
+28 -19
View File
@@ -8179,6 +8179,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;
@@ -8190,9 +8205,13 @@ namespace ts {
if (container.parent && container.parent.kind === SyntaxKind.ObjectLiteralExpression) {
// Note: this works because object literal methods are deferred,
// which means that the type of the containing object literal is already known.
const type = checkExpressionCached(<ObjectLiteralExpression>container.parent);
if (type) {
return type;
const contextualType = getContextualType(container.parent as ObjectLiteralExpression);
const literalType = checkExpressionCached(<ObjectLiteralExpression>container.parent);
if (contextualType && literalType) {
return getIntersectionType([contextualType, literalType]);
}
else if (contextualType || literalType) {
return contextualType || literalType;
}
}
}
@@ -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) {
@@ -8450,6 +8453,12 @@ namespace ts {
if ((isFunctionExpressionOrArrowFunction(func) || isObjectLiteralMethod(func)) &&
isContextSensitive(func) &&
func.kind !== SyntaxKind.ArrowFunction) {
const type = isObjectLiteralMethod(func)
? getContextualTypeForObjectLiteralMethod(func)
: getApparentTypeOfContextualType(func);
if (type === anyType) {
return anyType;
}
const contextualSignature = getContextualSignature(func);
if (contextualSignature) {
return contextualSignature.thisType;
@@ -0,0 +1,51 @@
//// [thisTypeInFunctions2.ts]
interface Arguments {
init?: (this: void) => void;
willDestroy?: (this: any) => void;
[propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any);
}
declare function extend(arguments: Arguments): void;
class Mixin {
stuff: number;
}
extend({
init() {
this
},
mine: 12,
bar() {
this.init();
},
foo() {
this.bar;
this.url
this.handler()
this.baz
this.willDestroy
}
})
//// [thisTypeInFunctions2.js]
var Mixin = (function () {
function Mixin() {
}
return Mixin;
}());
extend({
init: function () {
this;
},
mine: 12,
bar: function () {
this.init();
},
foo: function () {
this.bar;
this.url;
this.handler();
this.baz;
this.willDestroy;
}
});
@@ -0,0 +1,56 @@
=== tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts ===
interface Arguments {
>Arguments : Symbol(Arguments, Decl(thisTypeInFunctions2.ts, 0, 0))
init?: (this: void) => void;
>init : Symbol(Arguments.init, Decl(thisTypeInFunctions2.ts, 0, 21))
>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 1, 12))
willDestroy?: (this: any) => void;
>willDestroy : Symbol(Arguments.willDestroy, Decl(thisTypeInFunctions2.ts, 1, 32))
>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 2, 19))
[propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any);
>propName : Symbol(propName, Decl(thisTypeInFunctions2.ts, 3, 5))
>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 3, 87))
>args : Symbol(args, Decl(thisTypeInFunctions2.ts, 3, 97))
}
declare function extend(arguments: Arguments): void;
>extend : Symbol(extend, Decl(thisTypeInFunctions2.ts, 4, 1))
>arguments : Symbol(arguments, Decl(thisTypeInFunctions2.ts, 5, 24))
>Arguments : Symbol(Arguments, Decl(thisTypeInFunctions2.ts, 0, 0))
class Mixin {
>Mixin : Symbol(Mixin, Decl(thisTypeInFunctions2.ts, 5, 52))
stuff: number;
>stuff : Symbol(Mixin.stuff, Decl(thisTypeInFunctions2.ts, 6, 13))
}
extend({
>extend : Symbol(extend, Decl(thisTypeInFunctions2.ts, 4, 1))
init() {
>init : Symbol(init, Decl(thisTypeInFunctions2.ts, 10, 8))
this
},
mine: 12,
>mine : Symbol(mine, Decl(thisTypeInFunctions2.ts, 13, 6))
bar() {
>bar : Symbol(bar, Decl(thisTypeInFunctions2.ts, 14, 13))
this.init();
},
foo() {
>foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 17, 6))
this.bar;
this.url
this.handler()
this.baz
this.willDestroy
}
})
@@ -0,0 +1,87 @@
=== tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts ===
interface Arguments {
>Arguments : Arguments
init?: (this: void) => void;
>init : (this: void) => void
>this : void
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[]
}
declare function extend(arguments: Arguments): void;
>extend : (arguments: Arguments) => void
>arguments : Arguments
>Arguments : Arguments
class Mixin {
>Mixin : Mixin
stuff: number;
>stuff : number
}
extend({
>extend({ init() { this }, mine: 12, bar() { this.init(); }, foo() { this.bar; this.url this.handler() this.baz this.willDestroy }}) : void
>extend : (arguments: Arguments) => void
>{ 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; }
init() {
>init : () => void
this
>this : void
},
mine: 12,
>mine : number
>12 : number
bar() {
>bar : () => void
this.init();
>this.init() : any
>this.init : any
>this : any
>init : any
},
foo() {
>foo : () => void
this.bar;
>this.bar : any
>this : any
>bar : any
this.url
>this.url : any
>this : any
>url : any
this.handler()
>this.handler() : any
>this.handler : any
>this : any
>handler : any
this.baz
>this.baz : any
>this : any
>baz : any
this.willDestroy
>this.willDestroy : any
>this : any
>willDestroy : any
}
})
@@ -0,0 +1,26 @@
interface Arguments {
init?: (this: void) => void;
willDestroy?: (this: any) => void;
[propName: string]: number | string | boolean | symbol | undefined | null | {} | ((this: any, ...args:any[]) => any);
}
declare function extend(arguments: Arguments): void;
class Mixin {
stuff: number;
}
extend({
init() {
this
},
mine: 12,
bar() {
this.init();
},
foo() {
this.bar;
this.url
this.handler()
this.baz
this.willDestroy
}
})