You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Suggestion: allow get/set accessors to be of different types #2521
It would be great if there was a way to relax the current constraint of requiring get/set accessors to have the same type. this would be helpful in a situation like this:
class MyClass {
private _myDate: moment.Moment;
get myDate(): moment.Moment {
return this._myDate;
}
set myDate(value: Date | moment.Moment) {
this._myDate = moment(value);
}
}
Currently, this does not seems to be possible, and I have to resort to something like this:
class MyClass {
private _myDate: moment.Moment;
get myDate(): moment.Moment {
return this._myDate;
}
set myDate(value: moment.Moment) {
assert.fail('Setter for myDate is not available. Please use: setMyDate() instead');
}
setMyDate(value: Date | moment.Moment) {
this._myDate = moment(value);
}
}
This is far from ideal, and the code would be much cleaner if different types would be allowed.
I can see how this would be nice here (and this has been requested before although I can't find the issue now) but it's not possible whether or not the utility is enough to warrant it. The problem is that accessors are not surfaced in .d.ts any differently than normal properties since they appear the same from that perspective. Meaning there's no differentiation between the getter and setter so there's no way to a) require an implementation use an accessor rather than a single instance member and b) specify the difference in types between the getter and setter.
Reacted by Stefan Bauer, Seph Reed, Jakub Kisielewski, Michael Burkman, oxygen, LvChengbin, Helmut Hummel and Christopher Carman
Reacted by Clint Pamperin, David Rohmer, Sergey Linnik, Meithal, Krzysztof Kotowicz, Alpvax, Telokis, Joe Pea, Klemen Oslaj, Keegan Farley and 37 more
A JavaScript getter and setter with different types is perfectly valid and works great and I believe it is this feature's main advantage/purpose. Having to provide a setMyDate() just to please TypeScript ruins it.
Think also about pure JS libraries that will follow this pattern: the .d.ts will have to expose an union or any.
The problem is that accessors are not surfaced in .d.ts any differently than normal properties
Then this limitation should be fixed and this issue should stay open:
// MyClass.d.ts// Instead of generating:declareclassMyClass{myDate: moment.Moment;}// Should generate:declareclassMyClass{getmyDate(): moment.Moment;setmyDate(value: Date|moment.Moment);}// Or shorter syntax:declareclassMyClass{myDate: (get: moment.Moment,set: Date|moment.Moment);// and 'fooBar: string' being a shorthand for 'fooBar: (get: string, set: string)'}
Reacted by JP de la Torre, Douglas Henri, Lukas, The Reality Engineer, André Gaul, Aron Adler, Justin Lau, Zheeeng, Clint Pamperin, kanatkubash and 76 more
I realize this is just an opinion, but writing a setter such that a.x === y is not true immediately after a.x = y; is a giant red flag. How do consumers of a library like that know which properties have magic side effects and which don't?
Reacted by Junyoung/"Clare" Jang, Elias Skogevall, Kyrylo Chernyshov, Soroush, Michael Burkman, Felix Becker, JP Denford, Seyi Adebajo, Simon Weaver and chadiik
Reacted by Julia, Seph Reed, Hopeless Programmer, Khoi Pham, Sahin Yort, Caleb Williams, rzvc and Maxim
How do [you] know which properties have magic side effects and which don't?
In JavaScript it can be un-intuitive, in TypeScript the tools (IDE + compiler) will complain. Why do we love TypeScript again? :)
Reacted by JP de la Torre, Lukas, Aron Adler, Joe Pea, Sebastian Müller, Michael Scott Asato Cuthbert, Will Stott, Alexey Gerasimov, Anton Bessonov, valepu and 9 more
Reacted by Elias Skogevall, Jiří Brabec, Seyi Adebajo, Amal Jossy and Toni Tabak
A JavaScript getter and setter with different types is perfectly valid and works great and I believe it is this feature's main advantage/purpose.
This is arguing that JavaScript is weakly typed, so TypeScript should be weakly typed. :-S
Reacted by Junyoung/"Clare" Jang, Michael Burkman, Russell Dempsey and Seyi Adebajo
Reacted by Peter Flynn, Meithal, Dobes Vandermeer, Joe Pea, Seph Reed, Cyrus Frost, Elias Skogevall, The Reality Engineer, Juan Pablo de la Torre, Will Stott and 13 more
Reacted by Joe Pea, Elias Skogevall and Eric Ferreira
No one is arguing (as far as I can see) that accessors should be weakly typed, they are arguing that we should have the flexibility to define the type(s).
Often it is needed to copy a plain old object to object instances.
get fields(): Field[] {
return this._fields;
}
set fields(value: any[]) {
this._fields = value.map(Field.fromJson);
}
That is much nicer than the alternative and allows my setter to encapsulate the exact type of logic setters are made for.
Reacted by JP de la Torre, Douglas Henri, leon, Neil Rackett, jvasoya, Eric Miller, Patricio Ezequiel Hondagneu Roig, Alexander Mills, Chechetkin Ilya, Peter Flynn and 28 more
Reacted by Joe Pea, Juan Pablo de la Torre and Dillon Redding
I just tested with typescript@next (Version 1.8.0-dev.20151102), and also have an error.
~$ tsc --version
message TS6029: Version 1.8.0-dev.20151102
~$ cat a.ts
class A {
get something(): number {return 5;}
set something(x: any) {}
}
~$ tsc -t es5 a.ts
a.ts(2,2): error TS2380: 'get' and 'set' accessor must have the same type.
a.ts(3,2): error TS2380: 'get' and 'set' accessor must have the same type.
There's a Playground build for PR 42425 available for trying out and I'd appreciate any feedback on how it meets folks' needs here. Thanks! See comments in #42425 on how this works.
Reacted by gmurray81, somebody1234, Junior Dussouillez, valepu and Jesper van den Ende
Reacted by Jesper van den Ende
Reacted by lazytype, whzx5byb, Yann CABON, Juan Pablo de la Torre, GCastilho, valepu and Mateusz Turcza
Ryan, is the team planning to refine the DOM defs (in lib.dom.d.ts) if your PR goes through? Is that even possible using the current build process? It'd be a breaking change, but only for code that isn't technically correct (the best kind of correct).
Reacted by lazytype, Brian Schlenker, Brian Kim, whzx5byb and GCastilho
We'll take feedback on the DOM if this goes in. I think we'd still be keeping a pretty tight bar on what's allowed there; most of the DOM has coercing setters where the coercion is in the vein of similarly-disallowed coercions like "5" * "11". More "obvious" coercions like string -> URL seem like the kind of thing we'd be open to.
Reacted by James Bromwell, ExE Boss and Mateusz Turcza
we are forced to return only { x : number, y : number } on get x() or to accept only vec2 on set x().
While the ideal approach would be to get fully operable vec2 with all its methods and to set anything that has x and y.
In #42425, Ryan said "First, the type of the getter must be assignable to the type of the setter. In other words, the assignment obj.x = obj.x; must be legal assuming obj.x is writable at all.... These prevent novel unsoundness from occurring and makes this feature unimpactful from a type relational perspective, which limits its risk." It sounds like the incoming feature will not support your use case.
That said, look immediately above your post: #43662 was just opened, which points out that "[Element#style] is one where you can’t just make the set type string | CSSStyleDeclaration because assigning a CSSStyleDeclaration back to the accessor doesn’t work". Maybe follow / upvote that issue and watch for more discussion there?
There is no reason to think that separately typed getter/setter should not be used in a greenfield project. JavaScript isn't statically typed and it has its own benefits: more terse and expressive syntax. Not having to remember 200 different method names that do a single thing with slightly different input is a great thing! This is even more powerful when taking into account new ES features such as proxy. This is just very valuable to library author.
While we're at it how about adding differently typed getter/setter to interface also? E.g.
we are forced to return only { x : number, y : number } on get x() or to accept only vec2 on set x().
While the ideal approach would be to get fully operable vec2 with all its methods and to set anything that has x and y.
This already works in the proposed PR. See this example.
While we're at it how about adding differently typed getter/setter to interface also? E.g.
Only reason why I want this on interface is because this is the only way for proxy to work in typescript.
To be pedantic, it does work on interfaces (see example). What doesn't work is index signatures, which honestly I'm okay with, since it's impossible to write such a getter/setter pair without proxies, and by the time you're talking about proxies, static typing is a bit of a stretch.
Of course, tracking get/set separately does raise some new questions, especially around type refinement. For example:
typeSomeMap={get[key: string](): string|undefined;set[key: string](string): string;}function(map: SomeMap){map.alpha="beta";const{ alpha }=map;// Here I would expect alpha to be refined to "beta" because// we are treating the object as a "map". This follows current// refinement rules, and is what I'm interested in.}typeFoo={getalpha(): string|number,setalpha(value: string|boolean): string|boolean}function(foo: Foo){foo.alpha=true;const{ alpha }=foo;// What value is foo? We can't really refine our type here...}
While the former scenario is almost certainly the biggest use-case here (and the one I care about), keeping the desired logic breaks the latter; supporting the second case severely limits usefulness of the former without some additional mechanism for describing when these the compiler can make these refinements.
I think perhaps some additional mechanism for this needs to be drawn up.
Mike, you should make a playground example to illustrate what you think is going on. You say that you'd expect const {alpha} to be typed as "beta" (literal) following current refinement rules, but it actually narrows to string.
From that same Playground, even without the new feature you wind up with unsound / incorrect assumptions about property accessors. This is of course dumb:
classBar{private_x: string|number=1;getx(): string|number{returnthis._x;}setx(val: string|number){if(Number.isInteger(val)){this._x=`${val}`;}else{this._x=Number(val);}}}constb=newBar();b.x=5constx1=b.x;// numberconsole.log(`number? no, ${typeofx1}`);// logs "string"b.x="3";constx2=b.x;// stringconsole.log(`string? no, ${typeofx2}`);// logs "number"
but I'm sure there are more reasonable real-world use cases.
I would be happy enough if type refinement simply follow to the letters of what the declaration describe for now. map.alpha can just be string | undefined, foo.alpha can just be string | number.
Maybe extra fine type refinement can be declared somehow in the future but I think it's out of this issue's scope.
Activity
danquirk commentedon Mar 26, 2015
I can see how this would be nice here (and this has been requested before although I can't find the issue now) but it's not possible whether or not the utility is enough to warrant it. The problem is that accessors are not surfaced in .d.ts any differently than normal properties since they appear the same from that perspective. Meaning there's no differentiation between the getter and setter so there's no way to a) require an implementation use an accessor rather than a single instance member and b) specify the difference in types between the getter and setter.
irakliy81 commentedon Mar 27, 2015
Thank you for the quick reply, Dan. I'll follow the less elegant way. Thanks for the great work!
tkrotoff commentedon Jun 11, 2015
A JavaScript getter and setter with different types is perfectly valid and works great and I believe it is this feature's main advantage/purpose. Having to provide a
setMyDate()just to please TypeScript ruins it.Think also about pure JS libraries that will follow this pattern: the .d.ts will have to expose an union or
any.Then this limitation should be fixed and this issue should stay open:
RyanCavanaugh commentedon Jun 11, 2015
I realize this is just an opinion, but writing a setter such that
a.x === yis nottrueimmediately aftera.x = y;is a giant red flag. How do consumers of a library like that know which properties have magic side effects and which don't?tkrotoff commentedon Jun 11, 2015
In JavaScript it can be un-intuitive, in TypeScript the tools (IDE + compiler) will complain. Why do we love TypeScript again? :)
kitsonk commentedon Jun 11, 2015
This is arguing that JavaScript is weakly typed, so TypeScript should be weakly typed. :-S
tkrotoff commentedon Jun 11, 2015
C# allows it and that does not make this language weakly typedC# does not allow get/set of different types.kitsonk commentedon Jun 12, 2015
😉
paulwalker commentedon Oct 21, 2015
No one is arguing (as far as I can see) that accessors should be weakly typed, they are arguing that we should have the flexibility to define the type(s).
Often it is needed to copy a plain old object to object instances.
That is much nicer than the alternative and allows my setter to encapsulate the exact type of logic setters are made for.
danquirk commentedon Oct 21, 2015
Paul Walker (@paulwalker) the pattern you use there (a setter taking an
anyand a getter returning a more specific type) is valid today.paulwalker commentedon Oct 21, 2015
Dan Quirk (@danquirk) Great to know, thank you! It looks like I just needed to update my IDE plugin compiler for ST.
sccolbert commentedon Nov 2, 2015
Dan Quirk (@danquirk) That doesn't seem to work according to the playground (or version 1.6.2):
http://www.typescriptlang.org/Playground#src=%0A%0Aclass%20Foo%20%7B%0A%0A%20%20get%20items()%3A%20string%5B%5D%20%7B%0A%09%20%20return%20%5B%5D%3B%0A%20%20%7D%0A%20%20%0A%20%20set%20items(value%3A%20any)%20%7B%0A%09%20%20%0A%20%20%7D%0A%7D
jasongrout commentedon Nov 2, 2015
I just tested with typescript@next (Version 1.8.0-dev.20151102), and also have an error.
150 remaining items
RyanCavanaugh commentedon Mar 25, 2021
There's a Playground build for PR 42425 available for trying out and I'd appreciate any feedback on how it meets folks' needs here. Thanks! See comments in #42425 on how this works.
thw0rted commentedon Mar 25, 2021
Ryan, is the team planning to refine the DOM defs (in
lib.dom.d.ts) if your PR goes through? Is that even possible using the current build process? It'd be a breaking change, but only for code that isn't technically correct (the best kind of correct).RyanCavanaugh commentedon Mar 25, 2021
We'll take feedback on the DOM if this goes in. I think we'd still be keeping a pretty tight bar on what's allowed there; most of the DOM has coercing setters where the coercion is in the vein of similarly-disallowed coercions like
"5" * "11". More "obvious" coercions likestring -> URLseem like the kind of thing we'd be open to.Variant accessors (#42425)
hopeless-programmer-online commentedon Apr 16, 2021
Any updates?
In the following code example:
we are forced to return only
{ x : number, y : number }onget x()or to accept onlyvec2onset x().While the ideal approach would be to
getfully operablevec2with all its methods and tosetanything that hasxandy.thw0rted commentedon Apr 16, 2021
In #42425, Ryan said "First, the type of the getter must be assignable to the type of the setter. In other words, the assignment
obj.x = obj.x;must be legal assuming obj.x is writable at all.... These prevent novel unsoundness from occurring and makes this feature unimpactful from a type relational perspective, which limits its risk." It sounds like the incoming feature will not support your use case.That said, look immediately above your post: #43662 was just opened, which points out that "[
Element#style] is one where you can’t just make the set typestring | CSSStyleDeclarationbecause assigning aCSSStyleDeclarationback to the accessor doesn’t work". Maybe follow / upvote that issue and watch for more discussion there?pckhoi commentedon Apr 20, 2021
There is no reason to think that separately typed getter/setter should not be used in a greenfield project. JavaScript isn't statically typed and it has its own benefits: more terse and expressive syntax. Not having to remember 200 different method names that do a single thing with slightly different input is a great thing! This is even more powerful when taking into account new ES features such as proxy. This is just very valuable to library author.
While we're at it how about adding differently typed getter/setter to interface also? E.g.
Only reason why I want this on interface is because this is the only way for proxy to work in typescript.
shicks commentedon Apr 20, 2021
This already works in the proposed PR. See this example.
To be pedantic, it does work on interfaces (see example). What doesn't work is index signatures, which honestly I'm okay with, since it's impossible to write such a getter/setter pair without proxies, and by the time you're talking about proxies, static typing is a bit of a stretch.
pckhoi commentedon Apr 20, 2021
Hey there are value in slapping some typing info onto proxies. Why do we need to be extreme and say either proxy or static typing?
mike-marcacci commentedon Apr 20, 2021
I am extremely interested in this because it is a prerequisite for my proposal to fix the major soundness issue with index signatures in a way that has a fairly well-considered migration path (if I can say so myself).
Of course, tracking get/set separately does raise some new questions, especially around type refinement. For example:
While the former scenario is almost certainly the biggest use-case here (and the one I care about), keeping the desired logic breaks the latter; supporting the second case severely limits usefulness of the former without some additional mechanism for describing when these the compiler can make these refinements.
I think perhaps some additional mechanism for this needs to be drawn up.
thw0rted commentedon Apr 20, 2021
Mike, you should make a playground example to illustrate what you think is going on. You say that you'd expect
const {alpha}to be typed as"beta"(literal) following current refinement rules, but it actually narrows tostring.From that same Playground, even without the new feature you wind up with unsound / incorrect assumptions about property accessors. This is of course dumb:
but I'm sure there are more reasonable real-world use cases.
pckhoi commentedon Apr 20, 2021
I would be happy enough if type refinement simply follow to the letters of what the declaration describe for now.
map.alphacan just bestring | undefined,foo.alphacan just bestring | number.Maybe extra fine type refinement can be declared somehow in the future but I think it's out of this issue's scope.
ftzi commentedon May 26, 2021
Ryan Cavanaugh (@RyanCavanaugh) Close this? #42425
RyanCavanaugh commentedon May 26, 2021
Yep. Folks can track #43662 for possible loosening of the get/set relation.