Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upSuggestion: allow get/set accessors to be of different types #2521
Comments
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. |
Thank you for the quick reply, Dan. I'll follow the less elegant way. Thanks for the great work! |
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 Think also about pure JS libraries that will follow this pattern: the .d.ts will have to expose an union or
Then this limitation should be fixed and this issue should stay open: // MyClass.d.ts
// Instead of generating:
declare class MyClass {
myDate: moment.Moment;
}
// Should generate:
declare class MyClass {
get myDate(): moment.Moment;
set myDate(value: Date | moment.Moment);
}
// Or shorter syntax:
declare class MyClass {
myDate: (get: moment.Moment, set: Date | moment.Moment);
// and 'fooBar: string' being a shorthand for 'fooBar: (get: string, set: string)'
} |
I realize this is just an opinion, but writing a setter such that |
In JavaScript it can be un-intuitive, in TypeScript the tools (IDE + compiler) will complain. Why do we love TypeScript again? :) |
This is arguing that JavaScript is weakly typed, so TypeScript should be weakly typed. :-S |
|
|
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. |
@paulwalker the pattern you use there (a setter taking an |
@danquirk Great to know, thank you! It looks like I just needed to update my IDE plugin compiler for ST. |
@danquirk That doesn't seem to work according to the playground (or version 1.6.2): |
I just tested with typescript@next (Version 1.8.0-dev.20151102), and also have an error.
|
Ironically, after updating my Sublime linter, it no longer threw an error, which is using TypeScript 1.7.x. I've been assuming it is a forthcoming enhancement in 1.7+, so perhaps 1.8.0 regressed. |
I think Dan was mistaken. The getter and the setter must be of identical type. |
shame. would have been a nice feature for when writing page objects for use in protractor tests. I would have been able to write a protractor test: po.email = "[email protected]";
expect(po.email).toBe("[email protected]"); ... by authoring a page object: class PageObject {
get email(): webdriver.promise.Promise<string> {
return element(by.model("ctrl.user.email")).getAttribute("value")
}
set email(value: any) {
element(by.model("ctrl.user.email")).clear().sendKeys(value);
}
} |
What about that code requires the setter to be of type |
The getter will return a Maybe the following longer form of the protractor test makes it clearer: po.email = "[email protected]";
var currentEmail : webdriver.promise.Promise<string> = po.email;
expect(currentEmail).toBe("[email protected]") |
@RyanCavanaugh With the introduction of null annotations, this prevents code that allows calling a setter with null to set it to some default value. class Style {
private _width: number = 5;
// `: number | null` encumbers callers with unnecessary `!`
get width(): number {
return this._width;
}
// `: number` prevents callers from passing in null
set width(newWidth: number | null) {
if (newWidth === null) {
this._width = 5;
}
else {
this._width = newWidth;
}
}
} Could you consider atleast allowing the types to differ in the presence of |
This would really be a nice feature. |
So will this be a feature? |
@artyil it is closed and tagged By Design which indicates that currently there is not any plans to add it. If you have a compelling use case which you think overrides the concerns expressed above, you can feel free to make your case and additional feedback may make the core team reconsider their position. |
@kitsonk I think more than enough compelling use cases have been provided in the above comments. While the current design follows a common pattern of most other typed languages with this restriction, it is unnecessary and overly restrictive in the context of Javascript. While it is by design, the design is wrong. |
I agree. |
So, merely having the types match does not prevent surprising behavior, and indeed But more importantly, your argument ignores the point that TS can't really be opinionated about these things because TS needs to interop with existing JS apis, including major and unlikely to change things like the DOM. And as such, it just doesn't matter whether or not the api is ideal; what matters is that TS can't interop cleanly with such given apis. Now, you're forced to either re-implement whatever fallback logic the api used internally to coerce an out-of-getter-type value passed to the setter and thus type the property as the getters type, or to ignore type checking to add useless type assertions at each getters site and thus type the property as the setters type. Or, worse: do whatever TS happens to choose to annotate for the DOM, regardless of whether that is ideal. All those choices are bad. The only way to avoid that is to accept the need to represent JS apis as faithfully as possible, and here: that seems possible (admittedly without any knowledge of TS internals that may make this decidedly non-trivial). |
I suspect some of the new type declaration syntax they enabled more recently could make this expressible when it was less feasible before, so they should really consider reopening this issue... @RyanCavanaugh |
In an ideal world TS would interop with all JS APIs. But that's not the case. There are a bunch of JS idioms that can't be typed correctly in TS. However, I'd rather them focus on fixing things that actually lead to better programming idioms, not worse. While supporting this in a sane way would be nice, there are a dozen other things I'd personally rather them spend their limited time on first. This is way down on that list. |
@MikeyBurkman the question of priority is valid. My primary concern is @RyanCavanaugh's decision to Close this issue and write it off. Dismissing all the issues pointed out on this thread directly conflicts with Typescript's stated mission, which is to be a "superset" of Javascript (instead of a subset). |
Yeah I can definitely agree that this issue probably shouldn't be closed, as it's something that probably should be eventually fixed. (Though I really doubt it will be.) |
I think you are underselling an important aspect of TypeScript in that it's supposed to make it safer to use the existing DOM APIs and existing JS APIs. It exists to keep more than just your own code, within it's bubble, safe. Here's my point of view, I build component libraries, and while I wouldn't necessarily deliberately cause this mismatch between setters/getters to exist given my druthers. Sometimes my hand is forced based on how my libraries need to interface with other systems in-place. E.g. Angular sets all input properties on a component coming from markup as strings, rather than to do any type coercion based on their knowledge of the target type (at least, last I checked). So, do you refuse to accept strings? Do you make everything a string, even though this would make your types awful to use? Or do you do what TypeScript would have you do and use a type such as: string | Color but make using the getters awful. These are all pretty terrible options that reduce safety, when some extra expressiveness from the type system would have helped. The problem is, it's not just Angular that causes these issues. Angular wound up in that situation because it mirrors a lot of scenarios in the DOM where auto-coercion happens on a property set, but the getter is always an anticipated singular type. |
Look a bit upthread: Angular is much worse than you think. |
For me it is usually the issue when you want to create wrapper around some general-purpose storage, that can store key-value pairs and you want to limit users to certain keys. class Store {
private dict: Map<string, any>;
get name(): string | null {
return this.dict.get('name') as string | null;
}
set name(value: string) {
this.dict.set('name', value);
}
} You want to have such constraint: user can get |
@fan-tom, great use-case for why this issue needs to be reopened! Keep 'em coming. |
This issue has an extremely high number of up votes! This is TS team's project, so they can do as they see fit however they enjoy. But if they aim to make this as useful as possible for an external community of users, then I hope TS team can take the community's high number of votes into strong consideration.
TypeScript is a typed superset of a subset of JavaScript that compiles to plain JavaScript. |
More diplomatically, I think you could say it's an opinionated superset of JavaScript, when the team makes this kind of opinionated omission from the type system modelling. They are saying, "we won't model this, because it's hard for us to do, and we think you shouldn't do it, anyhow". But JavaScript is chock full of things you shouldn't do, and generally Typescript won't block you from doing these things if you have the will and know-how, because it seems like the general strategy is to not be opinionated about what JavaScript you can and can't just run as Typescript. That's why it's so strange to refuse to model this common (used in the DOM!) scenario, citing the belief that APIs shouldn't perform setter based coercion as the rationale to not do so. |
The best you can do is add a constructor and call the custom setter function there, if thats the only time you are setting that value.
The problem when assigning values directly still remains. |
Any update on this, after over 5.5 years? |
@xhliu as the labels indicate, it is a design limitation that is too complex to address, and therefore the issue is closed. I wouldn't not expect an update. The length of time for a closed issue is sort of irrelevant. |
Please reopen. Also this needs to be added at the interface level. A classic example is when implementing a Proxy where you have full flexibility over what you can read and write. |
https://ts-ast-viewer.com/#code/MYewdgzgLgBFCm0DyAjAVjAvDA3gKBkJgDMQQAuGAIhQEMAnKvAXzzwWXQDpSQg
The symbol foo has this to say:
There's a lot of room here for additional information, which is great design on TS behalf. Whoever designed this probably did so specifically to make additional features (even big ones) possible in the future. Speculative from this point on: If it was possible to declare (for pseudo code example) a Assuming the syntax exists to add that
Speculatively, it appears the hardest part of this challenge would likely be the amount of bike-shedding around syntax (or perhaps a company philosophy?), but from an implementation perspective the tools should all be there. A single condition would be added to the |
A lot of good examples have been written why this is desperately needed (especially for DOM and Angular). I'll just add that I got hit by this today when migrating old JS code to TS where assigning
|
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:
Currently, this does not seems to be possible, and I have to resort to something like this:
This is far from ideal, and the code would be much cleaner if different types would be allowed.
Thanks!