From 9f7621c7e26fcdd02ef15f0bbbe65f2d9104843a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 28 Apr 2016 14:17:11 -0700 Subject: [PATCH] Contextual signatures without thisType return anyType If a contextual signature is found, if its thisType is undefined, then the contextual type of `this` is now `any`. Previously `checkThisExpression` would keep looking for a different type for `this`. Also update tests to show this new behaviour. --- src/compiler/checker.ts | 2 +- .../reference/thisTypeInFunctions2.js | 96 ++++++++--- .../reference/thisTypeInFunctions2.symbols | 134 +++++++++++---- .../reference/thisTypeInFunctions2.types | 162 +++++++++++++----- .../types/thisType/thisTypeInFunctions2.ts | 56 ++++-- 5 files changed, 332 insertions(+), 118 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7a301884b4e..6cedb78686d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8453,7 +8453,7 @@ namespace ts { 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; diff --git a/tests/baselines/reference/thisTypeInFunctions2.js b/tests/baselines/reference/thisTypeInFunctions2.js index 7fc52877191..f52438a19ab 100644 --- a/tests/baselines/reference/thisTypeInFunctions2.js +++ b/tests/baselines/reference/thisTypeInFunctions2.js @@ -1,51 +1,91 @@ //// [thisTypeInFunctions2.ts] -interface Arguments { - init?: (this: void) => void; +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); } -declare function extend(arguments: Arguments): void; -class Mixin { - stuff: number; +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; -extend({ +extend1({ init() { - this + this // this: IndexedWithThis because of contextual typing. + // this.mine + this.willDestroy }, mine: 12, - bar() { - this.init(); - }, foo() { - this.bar; - this.url - this.handler() - this.baz + 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] -var Mixin = (function () { - function Mixin() { - } - return Mixin; -}()); -extend({ +extend1({ init: function () { - this; + this; // this: IndexedWithThis because of contextual typing. + // this.mine + this.willDestroy; }, mine: 12, - bar: function () { - this.init(); - }, foo: function () { - this.bar; - this.url; - this.handler(); - this.baz; + 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; + } +}); diff --git a/tests/baselines/reference/thisTypeInFunctions2.symbols b/tests/baselines/reference/thisTypeInFunctions2.symbols index 5e186e9485d..cdc7fb321d4 100644 --- a/tests/baselines/reference/thisTypeInFunctions2.symbols +++ b/tests/baselines/reference/thisTypeInFunctions2.symbols @@ -1,56 +1,124 @@ === tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts === -interface Arguments { ->Arguments : Symbol(Arguments, Decl(thisTypeInFunctions2.ts, 0, 0)) +interface IndexedWithThis { +>IndexedWithThis : Symbol(IndexedWithThis, 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)) + // 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(Arguments.willDestroy, Decl(thisTypeInFunctions2.ts, 1, 32)) ->this : Symbol(this, Decl(thisTypeInFunctions2.ts, 2, 19)) +>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, 3, 5)) ->this : Symbol(this, Decl(thisTypeInFunctions2.ts, 3, 87)) ->args : Symbol(args, Decl(thisTypeInFunctions2.ts, 3, 97)) +>propName : Symbol(propName, Decl(thisTypeInFunctions2.ts, 4, 5)) +>this : Symbol(this, Decl(thisTypeInFunctions2.ts, 4, 87)) +>args : Symbol(args, Decl(thisTypeInFunctions2.ts, 4, 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)) +interface IndexedWithoutThis { +>IndexedWithoutThis : Symbol(IndexedWithoutThis, Decl(thisTypeInFunctions2.ts, 5, 1)) -class Mixin { ->Mixin : Symbol(Mixin, Decl(thisTypeInFunctions2.ts, 5, 52)) + // this is what React would like to write (and what they write today) + init?: () => void; +>init : Symbol(IndexedWithoutThis.init, Decl(thisTypeInFunctions2.ts, 6, 30)) - stuff: number; ->stuff : Symbol(Mixin.stuff, Decl(thisTypeInFunctions2.ts, 6, 13)) + 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)) -extend({ ->extend : Symbol(extend, Decl(thisTypeInFunctions2.ts, 4, 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, 10, 8)) +>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)) - this }, mine: 12, ->mine : Symbol(mine, Decl(thisTypeInFunctions2.ts, 13, 6)) +>mine : Symbol(mine, Decl(thisTypeInFunctions2.ts, 25, 6)) - bar() { ->bar : Symbol(bar, Decl(thisTypeInFunctions2.ts, 14, 13)) - - this.init(); - }, foo() { ->foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 17, 6)) +>foo : Symbol(foo, Decl(thisTypeInFunctions2.ts, 26, 13)) - this.bar; - this.url - this.handler() - this.baz + 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; } }) diff --git a/tests/baselines/reference/thisTypeInFunctions2.types b/tests/baselines/reference/thisTypeInFunctions2.types index 3e3e9123549..371b5f5cafc 100644 --- a/tests/baselines/reference/thisTypeInFunctions2.types +++ b/tests/baselines/reference/thisTypeInFunctions2.types @@ -1,10 +1,11 @@ === tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts === -interface Arguments { ->Arguments : Arguments +interface IndexedWithThis { +>IndexedWithThis : IndexedWithThis - init?: (this: void) => void; ->init : (this: void) => void ->this : void + // this is a workaround for React + init?: (this: this) => void; +>init : (this: this) => void +>this : this willDestroy?: (this: any) => void; >willDestroy : (this: any) => void @@ -16,72 +17,149 @@ interface Arguments { >this : any >args : any[] } -declare function extend(arguments: Arguments): void; ->extend : (arguments: Arguments) => void ->arguments : Arguments ->Arguments : Arguments +interface IndexedWithoutThis { +>IndexedWithoutThis : IndexedWithoutThis -class Mixin { ->Mixin : Mixin + // this is what React would like to write (and what they write today) + init?: () => void; +>init : () => void - stuff: number; ->stuff : number + willDestroy?: () => void; +>willDestroy : () => void + + [propName: string]: any; +>propName : string } +interface SimpleInterface { +>SimpleInterface : SimpleInterface -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; } + 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 : 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 - 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; // this: any because 'foo' matches the string indexer >this.url : any >this : any >url : any - this.handler() ->this.handler() : any ->this.handler : any + this.willDestroy; +>this.willDestroy : any >this : any ->handler : 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; } - this.baz ->this.baz : any + init() { +>init : () => void + + this // this: any because the contextual signature of init doesn't specify this' type >this : any ->baz : 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 + } }) diff --git a/tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts b/tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts index c33c5062249..a574c7a07e9 100644 --- a/tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts +++ b/tests/cases/conformance/types/thisType/thisTypeInFunctions2.ts @@ -1,26 +1,54 @@ -interface Arguments { - init?: (this: void) => void; +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); } -declare function extend(arguments: Arguments): void; -class Mixin { - stuff: number; +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; -extend({ +extend1({ init() { - this + this // this: IndexedWithThis because of contextual typing. + // this.mine + this.willDestroy }, mine: 12, - bar() { - this.init(); - }, foo() { - this.bar; - this.url - this.handler() - this.baz + 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; + } })