Uh oh!
There was an error while loading. Please reload this page.
Typed 'this' in object literal methods - #14141
Conversation
Latest commit adds contextual |
Anders Hejlsberg (ahejlsberg)
commented
Feb 17, 2017
The one issue that remains for discussion here is whether the strongly typed |
There was a problem hiding this comment.
I like this change.
- Still need tests
- Braden Snell (@bradenhs) points out that the contextual typing code only applies to functions and object literal methods. It should apply to object literal getters/setters as well.
- I ran RWC to see what breaks. I found several good breaks, a few breaks that require a d.ts update with
ThisType(Object.defineProperty, knockout and react), and a couple of places that show thatthisneeds to be the intersection of the object literal type and the parameter type.
| } | ||
| function getThisTypeFromContextualType(type: Type): Type { | ||
| return applyToContextualType(type, t => { |
There was a problem hiding this comment.
shouldn't applyToContextualType just be named mapType and replace mapType? It doesn't do anything specific to contextual types that I could see, and it's better than the current mapType because it skips return values of undefined. #Resolved
There was a problem hiding this comment.
Yes, that makes sense.
| tests/cases/conformance/fixSignatureCaching.ts(640,13): error TS2304: Cannot find name 'window'. | ||
| tests/cases/conformance/fixSignatureCaching.ts(641,13): error TS2304: Cannot find name 'window'. | ||
| tests/cases/conformance/fixSignatureCaching.ts(704,18): error TS2339: Property 'prepareDetectionCache' does not exist on type '{}'. | ||
| tests/cases/conformance/fixSignatureCaching.ts(704,45): error TS2339: Property '_cache' does not exist on type '{ constructor: (userAgent: any, maxPhoneWidth: any) => void; mobile: () => any; phone: () => any; tablet: () => any; userAgent: () => any; userAgents: () => any; os: () => any; version: (key: any) => any; versionStr: (key: any) => any; is: (key: any) => any; match: (pattern: any) => any; isPhoneSized: (maxPhoneWidth: any) => any; mobileGrade: () => any; }'. |
There was a problem hiding this comment.
The purpose of this test isn't obvious without following the pointer back to issue #10697. Can you add one more comment at the top of the file like "this test originally failed by using 100% CPU and should now take less than 1 second to run". I'd like a faster way to know that these additional failures are not a problem.
| this // this: containing object literal type | ||
| this.mine | ||
| this.willDestroy | ||
| //this.willDestroy |
There was a problem hiding this comment.
just delete this, I think. #Resolved
Summary of RWC breaks [1]: two good breaks from contextual typing of function assignment. Two breaks that show that [1] Note that I haven't looked at all of RWC yet; it hit a stack overflow halfway through.
declarefunctionf(elt: HTMLElement): void;functionouter(){varself=this;varargs: Draggable;args.helper=function(){returnf(this);// error 'this: Draggable' is not assignable to 'HTMLElement'// Property 'accessKey' is missing in 'Draggable'};}This catches code that is just wrong. I looked at (This happened in two different projects.)
// Override the prototype method on CCBase.prototype.debug=function(){varself: C=this;// error type 'CBase' is not assignable to 'C'// Property '_subclassProperty' is missing in 'CBase'I'm not sure what to make of this. The comment makes it sound like the intention is to override the method on C, not on CBase. So probably this is a good error too.
Object.defineProperties(Array.prototype,{empty: {value: function(){returnthis.length===0;}}})It looks like
functiondefineProperty(c: any,name: string,defaultValue: any){varbackingFieldName="_"+name;Object.defineProperty(c.prototype,name,{get: function(): any{if(typeofthis[backingFieldName]==='undefined'){this[backingFieldName]=defaultValue;}returnthis[backingFieldName];},// code continues ...});};This would also be fixed by adding
interfaceMockJaxOptions{url?: string;responseTime?: number;status?: number;responseText?: string;responseHeaders: StringMap<string>;}$.mockjax({type: "GET",url: "blah/blah/blah",status: 200,contentType: "application/json",response: function(){this.responseText=JSON.stringify({stuff: "things"});// error: property 'responseText' does not exist on '{ type: string ... }'}});This is bad because |
More breaks:
publicgetEmitOutput(resource: URI,absoluteWorkersResourcePath: string): WinJS.TPromise<Modes.IEmitOutput>{// TODO@Ben technical debt: worker cannot resolve paths absolutelet model =this.resourceService.get(resource);letcssLinks: string[]=this.cssLinks||[];// Custom Renderer to fix href in imagesletrenderer=newMarked.marked.Renderer();let$this=this;renderer.image=function(href: string,title: string,text: string): string{letout='<img src="'+$this.fixHref(resource,href)+'" alt="'+text+'"';if(title){out+=' title="'+title+'"';}out+=(this.options&&this.options.xhtml) ? '/>' : '>';~~~~~~~!!!errorTS2339: Property'options'doesnotexistontype'MarkedRenderer'.~~~~~~~!!!errorTS2339: Property'options'doesnotexistontype'MarkedRenderer'.This seems like a good break. I can't find a property named 'options' in
ko.observable.fn['toInt']=function(){returnparseInt(this());~~~~~~!!!errorTS2349: Cannotinvokeanexpressionwhosetypelacksacallsignature.Type 'KnockoutObservableFunctions<any>' has no compatible call signatures.
}This seems like an easy fix to knockout's d.ts. It's just that nobody ever noticed that
privatesetupEvents(xhr: XMLHttpRequest,request: AjaxRequest){constprogressSubscriber=request.progressSubscriber;xhr.ontimeout=functionxhrTimeout(e){const{subscriber, progressSubscriber, request }=(<any>xhrTimeout);if(progressSubscriber){progressSubscriber.error(e);}subscriber.error(newAjaxTimeoutError(this,request));//TODO: Make betterer.~~~~!!!errorTS2345: Argumentoftype'XMLHttpRequestEventTarget'isnotassignabletoparameteroftype'XMLHttpRequest'.!!!errorTS2345: Property'onreadystatechange'ismissingintype'XMLHttpRequestEventTarget'.};This seems like a good break too. The comment even indicates that the author is suspicious that something is wrong.
exportfunctionmain(){describe('es5 decorators',()=>{it('should declare directive class',()=>{varMyDirective=Directive({}).Class({constructor: function(){this.works=true;}});~~~~~!!!errorTS2339: Property'works'doesnotexistontype'{ constructor: () => void; }'.expect(newMyDirective().works).toEqual(true);});This is probably another case where the type of the object literal needs to be intersected with the contextual type from the call signature. But it's in a test. It could just be a completely dynamic use of an undeclared property.
// tree benchmark in Reactimport{getIntParameter,bindAction}from'angular2/src/test_lib/benchmark_util';import*asReactfrom'./react.min';varTreeComponent=React.createClass({displayName: 'TreeComponent',render: function(){vartreeNode=this.props.treeNode;~~~~~!!!errorTS2339: Property'props'doesnotexistontype'{ displayName: string; render: () => any; }'.This is evidence that |
I just tried this out and thought this would work: But it looks like the getter isn't aware of the type of |
Braden Snell (@bradenhs) Nope, that is not correct. But the code right now specifically works with the contextual type of |
Anders Hejlsberg (ahejlsberg)
commented
Feb 23, 2017
With latest commits we now properly type |
| @@ -22,9 +21,6 @@ tests/cases/conformance/types/thisType/thisTypeInAccessorsNegative.ts(16,22): er | |||
| } | |||
| const contextual: Foo = { | |||
There was a problem hiding this comment.
Can you move this test case to thisTypeInAccessors.ts so that we can see the types?
Anders Hejlsberg (ahejlsberg)
commented
Mar 2, 2017
Latest commit puts all of the new functionality under the |
Arthur Ozga (aozgaa)
commented
Mar 2, 2017
In the elseif(type.flags&TypeFlags.TypeParameter&&(typeasTypeParameter).isThisType){if(inObjectTypeLiteral){writer.reportInaccessibleThisError();}writer.writeKeyword("this");} |
Anders Hejlsberg (ahejlsberg)
commented
Mar 2, 2017
Arthur Ozga (@aozgaa) No, that is an unrelated check (it reports an error if |
Anders Hejlsberg (ahejlsberg)
commented
Mar 2, 2017
Mohamed Hegazy (@mhegazy) I think this one is good to do. Want to take a look? |
Derek Wickern (dwickern)
commented
Jun 13, 2017
In this scenario, is it possible to infer |
Jessica Franco (Jessidhia)
commented
Jun 13, 2017
I haven’t tested it but it should be possible with generics. declarefunctionwrap<T,Fextends(this: T)=>any>(f: F): F |
Derek Wickern (dwickern)
commented
Jun 13, 2017
Unfortunately that doesn't seem to work without specifying |
With this PR we strongly type
thisin methods of object literals and provide a facility for controlling the type ofthisthrough contextual typing. The new behavior is only enabled in--noImplicitThismode.The type of the expression
thisin a method of an object literal is now determined as follows:thisparameter,thishas the type of that parameter.thisparameter,thishas the type of that parameter.--noImplicitThisis enabled and the containing object literal has a contextual type that includes aThisType<T>,thishas typeT.--noImplicitThisis enabled and the containing object literal has a contextual type that doesn't include aThisType<T>,thishas the contextual type.--noImplicitThisis enabledthishas the type of the containing object literal.thishas typeany.Some examples:
In a similar manner, when
--noImplicitThisis enabled and a function expression is assigned to a target of the formobj.xxxorobj[xxx], the contextual type forthisin the function expression isobj:In cases where an API produces a
thisvalue by transforming its arguments, a newThisType<T>marker interface can be used to contextually indicate the transformed type. Specifically, when the contextual type for an object literal isThisType<T>or an intersection includingThisType<T>, the type ofthiswithin methods of the object literal isT.In the example above, the
methodsobject in the argument tomakeObjecthas a contextual type that includesThisType<D & M>and therefore the type ofthisin methods within themethodsobject is{ x: number, y: number } & { moveBy(dx: number, dy: number): number }. Notice how the type of themethodsproperty simultaneously is an inference target and a source for thethistype in methods.The
ThisType<T>marker interface is simply an empty interface declared inlib.d.ts. Beyond being recognized in the contextual type of an object literal, the interface acts like any empty interface.Patterns similar to the above are used in several frameworks, including for example Vue and Ember. Using
ThisType<T>we can now more accurately describe those frameworks.Supercedes #8382. We revoked that PR because it always made the type of
thisin object literal methods be the type of the object literal. We now make that the default behavior, but allow the default to be overridden using aThisType<T>contextual type.