Typescript: Suggestion: allow get/set accessors to be of different types

Created on 26 Mar 2015  ·  125Comments  ·  Source: microsoft/TypeScript

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.

Thanks!

Design Limitation Suggestion Too Complex

Most helpful comment

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:
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)'
}

All 125 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 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:
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 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?

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? :)

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

This is arguing that JavaScript is weakly typed, so TypeScript should be weakly typed

C# allows it and that does not make this language weakly typed C# does not allow get/set of different types.

C# allows it and that does not make this language weakly typed C# does not allow get/set of different types.

:wink:

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.

@paulwalker the pattern you use there (a setter taking an any and a getter returning a more specific type) is valid today.

@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):
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

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.

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.

Even with the version of visual studio code (0.10.5 (December 2015)) that supports typescript 1.7.5 and with typescript 1.7.5 installed globally on my machine this is still a problem:

image

So what version this will be supported?
Thanks

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 any ?

The getter will return a webdriver.promise.Promise<string> yet the setter I want to assign a string value.

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 | null and | undefined ?

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.

After thinking about this some more. i think the issue here is really the complexity of the implementation. I personally find @Arnavion's example compelling, but the type system today treats getters/setters as regular properties. for this to work, both should have the same value. to support a read/write types would be a large change, i am not sure the utility here would be worth the implementation cost.

While I love TypeScript and appreciate all of the effort the team puts into it (really, you folks rock!), I must admit I'm disappointed in this decision. It would be a much better solution than the alternatives of getFoo()/setFoo() or get foo()/set foo()/setFooEx().

Just a short list of problems:

  • We currently assume properties have exactly one type. We'd now need to distinguish between the "read" type and "write" type of every property in every location
  • All type relationships become substantially more complex because we have to reason about two types per property instead of one (is { get foo(): string | number; set foo(): boolean } assignable to { foo: boolean | string | number }, or vice versa?)
  • We currently assume that a property, after being set, still has the type of the set value on the following line (which apparently is an wrong assumption in some people's codebases, wat?). We'd probably just have to "turn off" any flow control analysis on properties like this

Honestly, I really try to refrain from being prescriptive here about how to write code, but I truly object to the idea that this code

foo.bar = "hello";
console.log(foo.bar);

should ever print anything than "hello" in a language that attempts to have sane semantics. Properties should have behavior that is indistinguishable from fields to external observers.

@RyanCavanaugh while I agree with you on injected opinion, I can see one counter argument that _might_ just be very TypeScripty... Throwing something weakly typed at a setter, but having something always strongly typed returned, e.g.:

foo.bar = [ '1', 2 ];  // any[]
console.log(foo.bar);  // number[]: [ 1, 2 ]

Though personally I tend to think if you are going to be that magical, best to create a method so that the end developer can clearly understand that what will be bended, folded and mutilated.

Here is our use case for this feature. Our API introduced a capability that we named _Autocasting_. The main benefit is a streamlined developer experience who can eliminate the number of classes to import to assign properties for which the type is well defined.

For example a color property can be expressed as a Color instance or as a CSS string like rgba(r, g, b, a) or as an array of 3 or 4 numbers. The property is still typed as instance of Color, as it is the type of what you get when reading the value.

Some info about it: https://developers.arcgis.com/javascript/latest/guide/autocasting/index.html

Our users have been very happy to get that feature, reducing the number of imports needed, and are understanding perfectly that the type changes the line after the assignment.

Another example for this issue: https://github.com/gulpjs/vinyl#filebase

file.base = 'd:\\dev';
console.log(file.base); //  'd:\\dev'
file.base = null;
console.log(file.base); //  'd:\\dev\\vinyl' (returns file.cwd)

So the setter is string | null | undefined and the getter is just string. What type should we use in the type definitions for this library? If we use the former, the compiler would require useless null checks everywhere, If we use the latter, we won't be able to assign null to this property.

I have another example where I'd like the getter to return nullable, but where the setter should never allow null as input, stylized as:

class Memory {
    public location: string;
    public time: Date;
    public company: Person[];
}

class Person
{
    private _bestMemoryEver: Memory | null;

    public get bestMemoryEver(): Memory | null { // Might not have one yet
        return this._bestMemoryEver;
    }

    public set bestMemoryEver(memory: Memory) { // But when he/she gets one, it can only be replaced, not removed
        this._bestMemoryEver = memory;
    }
}

var someDude = new Person();
// ...
var bestMemory: Memory | null = someDude.bestMemoryEver;
//...
someDude.bestMemoryEver = null; // Oh no you don't!

I understand that it might be too much work to build some special logic for allowing getters/setters to differ on null, and it's not such a big deal for me, but it'd be nice to have.

@Elephant-Vessel philosophically I absolutely love the example, it represents the fickle nature of human beings well but I'm not convinced that it would not represent that even better by allowing null (or undefined) to be set. How can I model synapse failure in the system?

@aluanhaddad Why would you want synapse failure? I don't want that kind of bad stuff in my universe ;)

Any updates on this? What about having a default value when set to null or undefined?

I don't want the consumer to have to null check beforehand. Currently I have to make the setter a separate method instead, but I would like them to be the same.

Below is what I would like to have:

export class TestClass {
  private _prop?: number;

  get prop(): number {
    // return default value if not defined
    this._prop === undefined ? 0 : this._prop;
  }
  set prop(val: number | undefined) {
    this._prop = val;
  }
}

It seems to me that having the benefit of non-null checking comes with gotchas, and this is one of them. With strict null checking turned off, this is possible, but you don't get the compiler help to prevent null reference exceptions. However, if you want compiler assistance, I feel that should come with more support like having separate definitions for getters and setters with respect to at least nullability if nothing else.

The labels on the issue indicate it is a design limitation and the implementation would be considered too complex, which essentially means that if someone has a super compelling reason why this should be the case, it is not going anywhere.

@mhegazy @kitsonk I may be biased, but I feel this is a bug that popped up for strict null checking on a common pattern, especially in other curly brace like languages where they don't yet have null checking. A workaround would require the consumer to use the bang operator or check that it is actually never null (which is the point of never being null with using default values).

This breaks down once you add strict null checking because now it's technically different types. I'm not asking for strong different types to be set, but it seems like the design requirements to enable this would also enable strong different types as well.

An alternative design could be used for weak types, such that types like null and undefined would be special cased for interface definitions and d.ts files if they don't want to enable fully different types.

In response to https://github.com/Microsoft/TypeScript/issues/2521#issuecomment-199650959
Here is a proposal design that should be less complex to implement:

export interface Test {
  undefset prop1: number; // property [get] type number and [set] type number | undefined
  nullset prop2: number; // property [get] type number and [set] type number | null
  nilset prop3: number; // property [get] type number and [set] type number | null | undefined
  undefget prop4: number; // property [get] type number | undefined and [set] type number
  nullget prop5: number; // property [get] type number | null and [set] type number
  nilget prop6: number; // property [get] type number | null | undefined and [set] type number
}

It looks like there are some people watching this thread that are much more familiar with TypeScript, so maybe somebody who's still paying attention can answer a related question. On this Cesium issue I mentioned the get/set type limitation we're discussing here, and the Cesium folks said the pattern they're following comes from C# -- it's the implicit constructor.

Can TypeScript support implicit constructors? That is, can I say that myThing.foo always returns a Bar, and can be assigned a Bar directly, but can also be assigned a number which will be quietly wrapped in / used to initialize a Bar, as a convenience to the developer? If it's possible to do this by annotating Bar, or maybe specifically saying that "number is assignable to Bar<number>", it would address the use case discussed in the Cesium issue, and also many of the issues raised in this thread.

If not, do I need to suggest implicit constructor support in a separate issue?

The more I think / read about it, the more I'm certain that the "implicit constructor pattern" is going to need the feature described in this issue. The only way it's possible in vanilla JS is using object get/set accessors, because that's the only time that nominal assignment (= operator) is actually calling a user-defined function. (Right?) So, I think we really will need

class MyThing{
  set foo(b: Bar<boolean> | boolean);
  get foo(): Bar<boolean>;
}

which it sounds like @RyanCavanaugh thinks is not "sane semantics".

The simple fact is, there's a pretty popular JS library out there that uses this pattern, and it looks like it's difficult if not impossible to describe given existing TS constraints. I hope I'm wrong.

JavaScript already allows the pattern you describe. The _challenge_ is that the read and write side of types in TypeScript are, by design, assumed to be the same. There was a modification to the language to disallow assignment (readonly) but there are a few issues that have requested the _write only_ concept, which have been discussed as too complex for little real world value.

IMO, ever since JavaScript allowed accessors, people have potentially created confusing APIs with them. I personally find it confusing that something on assignment _magically_ changes to something else. Implicit anything, especially type conversion, is the bane of JavaScript IMO. It is exactly the flexibility that causes problems. With those type of conversions, where there is _magic_, I personally like to see methods being called, where it is a bit more explicit to the consumer that some sort of ✨ will occur and make a read only getter to retrieve values.

That doesn't mean real world usage doesn't exist, that is potentially sane and rationale. I guess it comes up to the complexity of splitting the entire type system into two, where types have to be tracked on their read and write conditions. That seems like a very non-trivial scenario.

I agree about the :sparkles: here, but I'm not arguing for or against using the pattern, I'm just trying to come along behind code that already uses it and describe the shape of it. It already works the way it works, and TS isn't giving me the tools to describe it.

For better or worse JS has given us the ability to turn assignments into function calls and people are using it. Without the ability to assign different types to set/get pairs, why even have set/get in ambient typings at all? Salsa doesn't have to know that a property is implemented with a getter and setter if it's always going to treat myThing.foo as a member variable of a single type regardless of which side of the assignment it's on. (Obviously, actual TypeScript compilation is another thing altogether.)

@thw0rted

It looks like there are some people watching this thread that are much more familiar with TypeScript, so maybe somebody who's still paying attention can answer a related question. On this Cesium issue I mentioned the get/set type limitation we're discussing here, and the Cesium folks said the pattern they're following comes from C# -- it's the implicit constructor.

C#'s implicit user defined conversion operators perform static code generation based on the types of values. TypeScript types are erased and do not influence runtime behavior (async/await edge cases for Promise polyfillers not withstanding).

@kitsonk I have to disagree in general with respect to properties. Having spent a fair amount of time with Java, C++, and C#, I absolutely love properties (as seen in C#) because they provide a critical syntactic abstraction (this is somewhat true in JavaScript). They allow for interfaces to segregate read/write capabilities in meaningful ways without using changing syntax. I hate seeing verbs wasted on trivial operations like getX() when getting X can be implicit.
IMO confusing APIs, many of which do as you say abuse accessors, stem more from too many _setters_ do magical things.

If I have a readonly, but live view over some data, say a registry, I think readonly properties are very easy to understand.

interface Entry {key: string; value: any;}

export function createRegistry() {
  let entries: Entry[] = [];
  return {
    register(key: string, value: any) {
      entries = [...entries, {key, value}];
    },
    get entries() {
      return [...entries];
    }
  }
}

const registry = createRegistry();

registry.register('hello', '您好');
console.log(registry.entries); //[{key: 'hello', value: '您好'}]
registry.register('goodbye', '再见');
console.log(registry.entries); //[{key: 'hello', value: '您好'}, {key: 'goodbye', value: '再见'}]

Sorry for the tangent, but I love accessors for this and think they are easy to understand but I am willing to be convinced otherwise and readability is my first concern.

When TypeScript limits JavaScript, it becomes more of a nuisance than an advantage. Isn't TypeScript meant to help developers communicate with each other?

Also, setters are called Mutators for a reason. If I wouldn't need any kind of conversion, I wouldn't use a setter, I would set the variable by myself.

porting javascript project to typescript. i met this problem..

This would be nice to use on angular @Input decorators. Because the value is passed from a template, there would make it much cleaner, in my opinion, to deal with different incoming object types.

Update: this appears to work for me

import { Component, Input } from '@angular/core';
import { flatMap, isString, isArray, isFalsy } from 'lodash';

@Component({
  selector: 'app-error-notification',
  templateUrl: './error-notification.component.html',
})

export class ErrorNotificationComponent {
  private _errors: Array<string> = [];
  constructor() { }
  /**
   * 'errors' is expected to be an input of either a string or an array of strings
   */
  @Input() set errors(errors: Array<string> | any){
      // Caller just passed in a string instead of an array of strings
      if (isString(errors)) {
        this._errors = [errors];
      }
      // Caller passed in array, assuming it is a string array
      if (isArray(errors)) {
        this._errors = errors;
      }
      // Caller passed in something falsy, which means we should clear error list
      if (isFalsy(errors)) {
        this._errors = [];
      }
      // At this point just set it to whatever might have been passed in and let
      // the user debug when it is broken.
      this._errors = errors;
  }

  get errors() {
    return this._errors;
  }
}

So were are we ? I would really like to have the possibility to return a different type with the getter than from the setter. For example:

class Field {
  private _value: string;

  get value(): string {
    return this._value;
  }

  set value(value: any) {
    this._value = String(value);
  }
}

This is what does 99% of the native implementations (if you pass a number to a (input as HTMLInputElement).value it will always return a string. In fact get and set should be considered as methods and should allow some:

set value(value: string);
set value(value: number);
set value(value: any) {
  this._value = String(value);
}
  // AND/OR
set value(value: string | number) {
  this._value = String(value);
}

@raysuelzer , when you say your code "works", doesn't ErrorNotificationComponent.errors return a type of Array<string> | any? That means you need type guards every time you use it, when you know it can only ever actually return Array<string>.

@lifaon74 , as far as I know there is no movement on this issue. I think a compelling case has been made -- multiple scenarios presented, tons of legacy JS code that can't be properly described in Typescript because of this -- but the issue is closed. Maybe if you think the facts on the ground have changed, open a new one? I don't know the team's policy on retreading old arguments, but I'd have your back.

I don't know the team's policy on retreading old arguments, but I'd have your back.

They will reconsider previously closed subjects. Providing a 👍 at the top of the issue puts credence in that it is meaningful. I believe that it has generally fallen under the "too complex" category because it would mean that type system would have to be bifurcated on every read and write and I suspect the feeling is the effort and cost of putting in that in to satiate what is a valid but somewhat uncommon use case isn't worth it.

My personal feeling is it would be a _nice to have_, especially for the being able to model existing JavaScript code that uses this pattern effectively.

I personally would consider the matter resolved if we came up with some not-totally-onerous workaround for the legacy JS issue. I'm still kind of a TS greenhorn, so maybe my current solution (forced casting or unnecessary type guards) is not an optimal use of existing capabilities?

Agree. The setter will be so much limited when type has to be the same... Please enhance.

Looking for recommendations on accomplishing this:

get price() {
    return (this._price as number);
  }

  set price(price) {
    this._price = typeof price === 'string' ? parseFloat(parseFloat(price).toFixed(8)) : parseFloat(price.toFixed(8));
  }

I want the stored value to be a number but I want to convert from string to float in the setter so I don't have to parseFloat everytime I set a property.

I think the verdict is that this is a valid (or at least reasonably well-accepted) Javascript pattern, that does not fit well with Typescript internals. Maybe if changing the internals is too big of a change, we could get some kind of annotation that changes how the TS compiles internally? Something like

class Widget {
  get price(): number | /** @impossible */ string | undefined { return this._price; }
  set price(val: number|string|undefined){ ... }
}

let w = new Widget();
w.price = 10;
// Annotation processes as "let p:number|undefined = (w.price as number|undefined)"
let p: number|undefined = w.price;

In other words, maybe we could mark up the TS such that the pre-processor (?) converts all reads of w.price to an explicit cast, before the TS is transpiled to JS. Of course, I don't know the internals of how TS manages annotations, so this might be total garbage, but the general idea is that maybe it would be easier to somehow magick the TS into other TS, than to change how the TS transpiler generates JS.

Not saying this feature isn't necessary but here's a way to bypass for set. IMO get should always return the same variable type, so this was good enough for me.

class Widget {
    get price(): number { return this._price; }
    set price(val){ return this.setPrice(val); } // call another function

    // do processing here
    private setPrice(price: number | string): number {
        let num = Number(price);
        return isNaN(num) ? 0 : num;
    }
}

@iamjoyce widget.price = '123' emits the compile error in your code

@iamjoyce => wrong because the compiler assumes val: number in set price(val)

Yeah, I'm also here because Angular's usage of components seems to want us to have different getter/setter types.

Angular will always inject a string if you set a components property (via an attribute) from markup (unless using a binding expression) regardless of the type of the input property. So it sure as heck would be nice to be able do model this like so:

private _someProperty: SomeEnum;
set someProperty(value: SomeEnum | string) {
   this._someProperty = this.coerceSomeEnum(value);
} 
get someProperty(): SomeEnum {
  return this._someProperty;
}

Today, this works if we leave out the | string but to have that there would more accurately describe how Angular ends up using the component. In that if you access it from code, the type of the property matters, but if you set it like an attribute, it will brute force a string in.

I don't think we generally want this functionality because we'd LIKE to design APIs this way. I agree, that properties are better if they don't do coercion side effects under the covers. To get around this, it would be great if Angular would have been designed such that attribute sets versus binding property sets would come into different entry points.

But if we look at this pragmatically, its helpful if TypeScript allows us to model the interactions with external libraries that happen to be using our types in such a way that defies the read/write type equality axiom.

In this instance, it would be highly problematic to use the union type on the getter, as it would require all sorts of annoying type guards/assertions. But leaving the union type off the setter feels wrong as any of the tooling could decide down the road to start trying to verify that properties being set from attributes should be assignable from string.

So in this case, the type system isn't expressive enough to capture how an external library is being permitted to use the type. This doesn't _immediately_ matter, as the external library does not actually consume these types at the typescript level, with type information. But may eventually matter, as the tooling may very well consume the type information.

One person above mentioned an alternate solution that seems a bit obnoxious, but could probably work, whereby we could use the union type for both the setter/getter, but have some way of indicating that certain types from the union are impossible for the getter. So are, in effect, pre-removed from consideration in the getter call site site control flow as if someone had used a type guard to check they aren't present. That seems annoying compared to allowing for a superset union on the setter compared to a subset union on the getter, though.

But perhaps that's a way to resolve things internally. As long as the setter is just a superset union of the getter type. Treat the types of the getter and setter as equivalent internally, but mark portions of the getter's union type as impossible. So that the control flow analysis removes them from consideration. Would that get around the design constraints?

To elaborate on the above. Maybe it would be a useful thing, from a type expressiveness standpoint to be able to indicate portions of a compound type as being impossible. This wouldn't impact equality with another type with the same structure but without the impossible modifiers. An impossible modifier would only impact the control flow analysis.

As an alternate suggestion, if there were some syntax to apply a user defined type guard to a return value, this would also suffice. I realize that this is, under normal circumstances, a bit ridiculous, but having that sort of expressiveness on the return value as well as on the arguments would help resolve these edge cases.

The user defined type guard on the return value, also has the benefit of being something that might be expressible in type declarations/interfaces when squashed to a property?

Just adding another use case here, mobx-state-tree allows setting a property in a couple of different ways (from instances and snapshots types), however it only ever will return it in a single standard way (instances) from the get accessor , so this would be extremely useful if it was supported.

I'm getting around this like so:

interface SomeNestedString {
  foo: string;
}

...

private _foo: SomeNestedString | string;

get foo(): SomeNestedString | string {
  return this._foo;
}

set foo(value: SomeNestedString | string) {
  this._foo = (value as SomeNestedString).foo;
}

I don't think that works around the issue at hand. You'd still need type guards when using the getter, even though, in reality, the getter only ever returns a subset of the union type.

I'm using any to work around TSLint. And the compiler doesn't complain either.

export class FooBar {
  private bar: string;

  get foo (): string | any {
    return this.bar;
  }

  set foo (value: Date | string | any) {
    // Type guarding enables IntelliSense in VS Code
    if (value instanceof Date) {
      this.bar = value.toISOString();
    } else if (typeof value === 'string') {
      this.bar = value;
    } else {
      this.bar = String(value); // Or throw an error
    }
  }
}

@jpidelatorre this way you lose type safety.

const fooBar = new FooBar()
const a: number = fooBar.foo // works while it should fail
fooBar.foo = 123 // fails only at runtime, not compile time. It doesnt fail in this particular case with strings and numbers, but it will with something more complex

@keenondrums That's exactly why we want accessors to have different types. What I found is a workaround, not a solution.

@jpidelatorre my current workaround is to use another function as setter

export class FooBar {
  private bar: string;

  get foo (): string {
    return this.bar;
  }

  setFoo (value: Date | string ) {}
}

@keenondrums Not as good looking as accessors, but the best option yet.

Not really a model for what happens with @input properties on an Angular component, unless you use a separate function for a getter.

Which is just too ugly.

I would also like this feature, but need it to work well in .d.ts files, where there are no workarounds. I am trying to document some classes exposed via Mocha (the Objective-C/Javascript bridge), and instance properties of wrapped items are set up like so:

class Foo {
    get bar:()=>number;
    set bar:number;
}

const foo = new Foo();
foo.bar = 3;
foo.bar(); // 3

I have also seen plenty of cases where an API allows you to set a property with an object matching an interface, but the getter always returns a real class instance:

interface IFoo {
    bar: string;
}

class Foo implements IFoo {
    bar: string;
    toString():string;
}

class Example {
    get foo:Foo;
    set foo:Foo|IFoo;
}

I had largely forgotten about this issue, but a thought occurred to me while reading back over it. I think we've settled on the idea that this is worth doing in the abstract, but too technically complicated to be feasible. That's a tradeoff calculation -- it's not technically impossible, it's just not worth the time and effort (and added code complexity) to implement. Right?

Does the fact that this makes it impossible to describe the core DOM API accurately change the math at all? @RyanCavanaugh says

I truly object to the idea that this code foo.bar = "hello"; console.log(foo.bar); should ever print anything than "hello" in a language that attempts to have sane semantics.

We could argue about whether it should be used in this way, but the DOM has always supported constructs like el.hidden=1; console.log(el.hidden) // <-- true, not 1. This isn't just a pattern that a few people use, it's not just that it's in a popular library so it might be a good idea to support it. It's a core tenet of how JS has always worked -- a bit of DWIM baked into the soul of the language -- and making it impossible at the language level breaks the foundational principle of TS, that it must be a "superset" of JS. It's an ugly bubble sticking out of the Venn diagram, and we shouldn't forget it.

This is why I still like the idea of keeping the setter/getter having the same type:

number | boolean

But introducing some sort of typegaurd where you can indicate that, while the getter technically has the same union type, in reality it will only ever return a subset of the types in the union. Doesn't treating it as some sort of narrowing gaurd on the getter (an inference flow thing?) make it more straightforward than a modification to the type model? (He says not knowing anything about the internals...)

This could, alternatively, just be implied if the type used on the getter was a strict subset of the setters type?

This could, alternatively, just be implied if the type used on the getter was a strict subset of the setters type?

👍 !

I think that we can all agree that you shouldn't be able to get a type that wasn't settable; I'd just like some way to automatically narrow the type on get to be the type that I know it will always be.

I don't understand why some are objecting that it would violate type safety and all. I don't see it violates any type safety if we allow atleast the setter to have different type, because any how we have a check in place that you will not allow other type to set to a property.
Ex:
Say I have a property as Array but from DB this will be returned as an string with comma separation as like say '10,20,40'. But I can't map that to the mode property now, so It would be very helpful if you could allow like

private _employeeIdList : number[]

get EmployeeIDList(): number[] {
return this._employeeIdList ;
}
set EmployeeIDList(_idList: any) {
if (typeof _idList== 'string') {
this._employeeIdList = _idList.split(',').map(d => Number(d));
}
else if (typeof _idList== 'object') {
this._employeeIdList = _idList as number[];
}
}

I would have easily solved this problem and it is perfectly type safe, eventhough you allow an different type in SET but still it stops us from assigning wrong type to the property. So win win. I hope the team members will put down their ego and try to think the problem it creates and fix this.

I'd like to chime in that I still think this is really important for modeling the behavior of existing JS libraries.

While it's all well and good to turn up noses and define a setter that coerces a type to a subset of the incoming type and always returns that subset in the getter as a code smell, in reality, this is a reasonably common pattern in JS land.

I think TypeScript is lovely in that it allows us to be very expressive when describing the cagaries of existing JS APIs, even if they don't have ideally structured APIs. I think this is a scenerio where if TypeScript were expressive enough to allow us to indicate that the getter would always return a type which is the strict subset of the getter's type, this would add a tremendous amount of value in modeling existing APIs, even the DOM!

Trusted Types is a new browser API proposal to fight DOM XSS. It is already implemented in Chromium (behind a flag). The bulk of the API modifies setters of various DOM properties to accept Trusted Types. For example, .innerHTML accepts TrustedHTML | string while it always returns string. To describe the API in TypeScript, we would need this issue to be fixed.

The difference from the previous comments is that this is a browser API (and not a user-land library) which couldn't be changed easily. Also the impact of changing the type of Element.innerHTML to any (which is the only currently possible solution) is bigger than imprecisely describing a user-land library.

Is there a chance that this pull-request will be re-opened? Or are there other solutions I missed?

Cc: @mprobst, @koto.

In a language that supports type union such as TypeScript, this feature is natural and a selling point.
 
@RyanCavanaugh even when the getter and setter have the same type, it's not guaranteed that o.x === y after o.x = y as the setter may do some sanitization before saving the value.

element.scrollTop = -100;
element.scrollTop; // returns 0

I second the concern by @vrana. The current behaviour makes it impossible to model some of the existing APIs in Typescript.

This is especially true for Web APIs, for a lot of which the setter and getter types are different. In practice web API setters perform all sorts of type coercion, some of them specced directly for a given browser feature, but most of them implicitly via IDL. A lot of the setters do mutate the value as well, see e.g. Location interface spec. This is not a single developer mistake - it's a specification of the API that the web developers code against.

Narrowing the getter types allows Typescript to represent those APIs, which is now impossible.

You can represent those APIs because it's correct to say that the type of the property is the union of the possible types that you can provide to the setter or get from the getter.

It just isn't _efficient_ to describe an API that way. You are requiring the consumer to use a type guard in every instance that they use the getter to narrow the possible types.

That's ok if you haven't made it axiomatic that you'll always return a narrowed type from a getter, as many Web APIs even lock down in their spec.

But even setting that aside for a moment, and talking about user APIs, a strong use case for accepting union types on a setter is the "scripty" one. We want to accept a range of discrete types that we can acceptably coerce to the type that we actually want.

Why allow for that? Ease of use.

That may not matter for APIs you are developing internally for your own team's use, but can matter a lot for APIs designed for public, generalized consumption.

Its lovely that TypeScript can allow us to precisely describe an API that has relaxed the types acceptable for some of it's properties, but that benefit is marred by the friction on the other end where excessive type checking/guarding is required to determine the getter return type, which we'd prefer to _specify_.

I'd argue that's a different scenario than the idealized case @RyanCavanaugh posits. That case implies that you getter and setter should always have the same union type because your backing field also has the same union type, and you will always just round trip that value, and to change it's type around is just nonsensical.

I think that case centers around a more idealized usage of union types, whereby you are dealing with constructed types and really are treating that union type as a semantic unit, which you should probably have created an alias for.

type urlRep = string | Url;

Most things will just round trip it, and deal with only common props, and in some cases you'll break the black box with some type guards.

I'd argue that's an entirely different scenario from what we are describing here. What we are describing is the reality that general/public usage APIs, especially for use in scripting languages like JavaScript often deliberately relax the acceptable types for a setter, because there's a range of types they'll agree to coerce to the ideal type, so they offer it as a quality of life improvement to accept all of those.

This kind of thing may seem nonsensical if you are both the producer and consumer of an API, as it just makes for more work, but can make a lot of sense if you are designing an API for mass consumption.

And I don't think anyone would ever want for the setter/getter to have _disjoint_ types. What is being discussed here is that the API producer assert to the API consumer that the getter will return a value with a type that is a strict subset of the union type of the setter.

And I don't think anyone would ever want for the setter/getter to have disjoint types.

Just to provide a counter viewpoint to that. I would definitely want that because having disjoint types for setter/getter is completely legal in javascript. And I think the primary goal ought to be making the typesystem expressive enough to correctly type anything that is legal in javascript (at least at the .d.ts level). I understand it's not a good practice, neither are global objects for example, or changing the prototype of builtins like function etc, however we still are able to type those just fine in .d.ts files and I haven't heard anyone regret that we can do that (even though it does cause some poorly designed type definition files to appear on definitely typed).

Just ran into a situation where I need this to be a feature in order to have the correct types which I do not control. The setter needs to be more liberal and coerce to a more conservative type which can be consistently returned by the getter.

I really wish this feature existed. I'm porting JavaScript code to TypeScript, and it's a real bummer that I'll have to refactor the setters if I want type safety.

For example, I've got something like this:

class Vector3 { /* ... */ }

type XYZ = Vector3 | [number, number, number] | {x: number, y: number, z: number}
type PropertyAnimator = (x: number, y: number, z: number, timestamp: number) => XYZ
type XYZSettables =  XYZ | PropertyAnimator

export class Transformable {
        // ...

        set position(newValue: XYZSettables) {
            this._setPropertyXYZ('position', newValue)
        }
        get position(): Vector3 {
            return this._props.position
        }

        // ...
}

And as you can imagine, the usage is very flexible:

const transform = new Transformable

// use an array
transform.position = [20, 30, 40]

// use an object
transform.position = {y: 30, z: 40} // skip `x` this time

// animate manually, a property directly
requestAnimationFrame((time) => {
  transform.position.x = 100 * Math.sin(time * 0.001)
})

// animate manually, with an array, which could be shared across instances
const pos = [10, 20, 30]
requestAnimationFrame((time) => {
  pos[2] = 100 * Math.sin(time * 0.001)
  transform.position = pos
})

// Animate with a property function
transform.position = (x, y, z, time) => [x, y, 100 * Math.sin(time * 0.001)]

// or a simple increment:
transform.position = (x, y, z) => [x, y, ++z]

// etc

// etc

// etc

This is 4 years old. How is it STILL not fixed? This feature, as mentioned in the above comments, is significant! At least consider re-opening the issue?

And I completely agree with @kitsonk:

@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.

TypeScript 3.6 introduced syntax for typing accessors in declaration files, whilst retaining the restriction that the type of the getter and setter must be identical.

Our UI toolkit relies heavily on the auto-casting setters, where the setter accepts more types than the singular type returned by the getter. So it would be nice if TS offered a way to make this pattern type-safe.

It looks like @RyanCavanaugh gave the most concrete rebuttal of this feature 3 years ago. I wonder if the recently reported DOM use-cases as well as the availability of new declaration syntax might allow a new decision?

Yes, as soon as I saw that new feature, I immediately thought of this feature request also. I also want it for the purposes of our UI frameworks. This is a real use case, guys.

The TSConf 2019 will be held on October 11th, thinking this would be a great retrospection in Q&A session, if someone got the chance 🤔

I may have said this before on this long thread, but I think it bears reiteration.

IMO the expressive type system in TypeScript serves two discrete purposes. The first is allowing you to write safer new code. And if that was the only purpose, perhaps you could make an argument that you could avoid this use case as code may be safer if you were to disallow this scenario (but I think we could still have an argument about this).

However the second purpose is to capture the behavior of libraries as they are and it's a reasonably common practice in the DOM and in JS libraries to auto-coerce in the setter, but return one expected type in the getter. To allow us to capture this in the type system allows us to more accurately describe existing frameworks as they are.

At very least, I think Design Limitation could be removed here, I don't think that pertains any longer.

This apparently is the reason for the fact that per #33749 .style.display = ...something nullable... no longer typechecks; which is presented as a nullablity correctness issue. That's being a little disingenious isn't it? It's annoying to have to figure out new breaking changes when they're mislabeled as bugfixes (I went looking for actual issues this might have caused). Personally, I find it a lot less surprising that null has special behavior "use default", than that the empty string does; and until typescipt 3.7 I preferred to use null to capture that. In any case, it would be nice if intentionally incorrect type annotations made to workaround this limitation were clearly labelled as such, to save time triaging upgrade issues.

I'm also interested in finding a path forward for this. What if it was only allowed in ambient contexts? @RyanCavanaugh would that help address your complexity concerns?

My use case for this is I have an API where a proxy returns a promise, but a set operation does not set a promise. I can't describe this in TypeScript right now.

let post = await loadPost()
let user = await loadUser()
post.author = user // Proxy handles links these two objects via remote IDs
await save(post)

// Somewhere else in code
let post = await loadPost()
let author = await post.author

Whether type correcting native functions, or proxies in general, TypeScript seems to be of the stance that those types of features were a mistake.

They really should take #6 and #7 off this list (https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals#goals)

My issue

I would like to push a item to an array in set method and get the whole array in the get method. For now I'll have to use set myArray(...value: string[]) which works fine. Bumbed in to this issue a lot... please consider removing this. A info or warning would work just fine for this.

Example (what I would like to do)

class MyClass {
   _myArray: string[] = [];

   set myArray(value: string) {
      this._myArray.push(value);
   }

   get myArray(): string[] {
      return this._myArray;
   }
}

Example (what I have to do)

class MyClass {
   _myArray: string[] = [];

   set myArray(...value: string[]) {
      this._myArray.push(value[0]);
   }

   get myArray(): string[] {
      return this._myArray;
   }
}

One solution I came up with when I needed this was to set the property to be a union type. This is because my time comes in as a string from the API, but I need to set as a Moment object for my UI.

My Example

class TimeStorage {
    _startDate: string | Moment = ""
    _endDate: string | Moment = ""

    set startDate(date: Moment | string) {
        this._startDate = moment(date).utc()
    }
    get startDate(): Moment | string {
        return moment.utc(this._startDate)
    }

    set endDate(date: Moment) { 
        this._endDate = moment.utc(date)
    }
    get endDate() { 
        return moment.utc(this._endDate)
    }
}

I'm a bit late to the party but I've recently run into this issue myself. It is an interesting dilemma so I fiddled around with it a bit.

Let's assume this is something we want to make, a simple class that has a property that accepts strings and numbers (for whatever purpose) but the property is really stored as a string:

class Foo {
    constructor() {
        this._bar = '';
        this.baz = '';
    }

    // error: 'get' and 'set' accessor must have the same type.(2380)
    get bar(): string {
        return this._bar;
    }

    // error: 'get' and 'set' accessor must have the same type.(2380)
    set bar(value: string | number) {
        this._bar = value.toString();
    }

    public baz: string;
    private _bar: string;
}

Since this is not possible and we still want strong typing we need some extra code. Now let's pretend for our example's purpose that someone had made a Property library that looked like this:

/**
 * Abstract base class for properties.
 */
abstract class Property<GetType, SetType> {
    abstract get(): GetType;
    abstract set(value: SetType): void;
}

/**
 * Proxify an object so that it's get and set accessors are proxied to
 * the corresponding Property `get` and `set` calls.
 */
function proxify<T extends object>(obj: T) {
    return new Proxy<any>(obj, {
        get(target, key) {
            const prop = target[key];
            return (prop instanceof Property) ? prop.get() : prop;
        },
        set(target, key, value) {
            const prop = target[key];
            if (prop instanceof Property) {
                prop.set(value);
            } else {
                target[key] = value;
            }
            return true;
        }
    });
}

Using this library we could implement our typed property for our class like this:

class Bar extends Property<string, string | number> {
    constructor(bar: string | number) {
        super();
        this.set(bar);
    }

    get(): string {
        return this._bar;
    }

    set(value: string | number) {
        this._bar = value.toString();
    }

    private _bar: string;
}

class Foo {
    constructor() {
        this.bar = new Bar('');
        this.baz = '';
    }

    public bar: Bar;
    public baz: string;
}

And then, using this class would have type safe getter and setter for bar:

const foo = new Foo();

// use property's typed setter
foo.bar.set(42);
foo.baz = 'foobar';

// use property's typed getter
// output: 42 foobar
console.log(foo.bar.get(), foo.baz);

And if you need to use the property setters or getters more dynamically, you can use the proxify function to wrap it:

const foo = new Foo();

// use proxified property setters
Object.assign(proxify(foo), { bar: 100, baz: 'hello world' });

// use property's typed getter
// output: 100 hello world
console.log(foo.bar.get(), foo.baz);

// use proxified property getters
// output: {"bar":"100","baz":"hello world"}
console.log(JSON.stringify(proxify(foo)));

Disclaimer: If someone wants to take this code and put it in a library or do whatever he/she wishes with it, please feel free to do so. I'm too lazy.

Just a few extra notes and then I promise I'll stop spamming this long list of people!

If we add this to the imaginary library:

/**
 * Create a property with custom `get` and `set` accessors.
 */
function property<GetType, SetType>(property: {
    get: () => GetType,
    set: (value: SetType) => void
}) {
    const obj = { ...property };
    Object.setPrototypeOf(obj, Property.prototype);
    return obj;
}

We get an implementation that is a bit closer to the original class, and most importantly has access to this:

class Foo {
    constructor() {
        this._bar = '';
        this.baz = '';
    }

    public bar = property({
        get: (): string => {
            return this._bar;
        },
        set: (value: string | number) => {
            this._bar = value.toString();
        }
    });

    public baz: string;
    private _bar: string;
}

It's not the prettiest thing in the world but it works. Maybe someone can refine on this and make something that's actually nice.

In the unlikely event that any individual reading this thread isn't convinced that this feature is important, I give you this incredibly ugly hack that Angular just introduced:

It would be ideal to change the type of value here, from boolean to boolean|'', to match the set of values which are actually accepted by the setter. TypeScript requires that both the getter and setter have the same type, so if the getter should return a boolean then the setter is stuck with the narrower type.... Angular supports checking a wider, more permissive type for @Input() than is declared for the input field itself. Enable this by adding a static property with the ngAcceptInputType_ prefix to the component class:

````
class SubmitButton {
private _disabled: boolean;

get disabled(): boolean {
return this._disabled;
}

set disabled(value: boolean) {
this._disabled = (value === '') || value;
}

static ngAcceptInputType_disabled: boolean|'';
}
````

Please, please fix this.

Yup, we literally have this ugliness all over the place to support the patterns Angular wants to use. The current restriction is too opinionated.

If TS wants to be used to model the full breadth of libraries in web land, then it needs to strive to be less opinionated, or it will fail to model all designs.

At this point, I believe the onus is on the contributors to communicate why this issue is not being re-opened? Is there a contributing member that is even strongly against this at this point?

We're building a DOM library for NodeJs that matches the W3C spec, but this Typescript issue makes it impossible. Any update?

It's shocking to me that some within TypeScript's core team is against a feature that is REQUIRED in order to recreate one of the most used JavaScript libraries on the planet: the DOM.

There is no other way to a) implement many parts of the the W3C DOM specification while b) using TypeScript's type system (unless you resort to using any all over the place, which defeats the entire purpose of TypeScript).

The homepage of typescriptlang.org is currently inaccurate when it states:

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.

The inability to recreate JavaScript's DOM specification shows that TypeScript is still a subset of Javascript NOT a superset.

In regards to implementing this feature, I agree with @gmurray81 and many others who have argued that the getter's type must be a subset of a setters's union type. This ensures no disjointed typing. This approach allows a setter's function to clean the input without destroying the getter's type (i.e., being forced to resort to using any).

Here's what I can't do. Something simple that I've done similar in JS but I can't in TS.

4yo issue that REALLY could be implemented.

export const enum Conns {
  none = 0, d = 1, u = 2, ud = 3,
  r = 4, rd = 5, ru = 6, rud = 7,
  l = 8, ld = 9, lu = 10, lud = 11,
  lr = 12, lrd = 13, lru = 14, lrud = 15,
  total = 16
}

class  Tile {
  public connections = Conns.none;

  get connLeft() { return this.connections & Conns.l; };

  set connLeft(val: boolean) { this.connections = val ? (this.connections | Conns.l) : (this.connections & ~Conns.l); }
}

Running into this issue now. The below seems like a sensible solution, to quote @calebjclark:

In regards to implementing this feature, I agree with @gmurray81 and many others who have argued that the getter's type must be a subset of a setters's union type. This ensures no disjointed typing. This approach allows a setter's function to clean the input without destroying the getter's type (i.e., being forced to resort to using any).

For for the getter being a subset of the of the setter's type, I think that's unnecessarily limited. Really, a setter's type _could_ theoretically be literally anything, as long as the implementation always returns a subtype of the getter's type. Requiring the setter be a supertype of the getter's type is kind of a weird limitation that might meet the couple of use cases presented in this ticket, but would surely get complaints later.

That being said, classes are implicitly also interfaces, with getters and setters being essentially implementation details that wouldn't appear in an interface. In OP's example, you'd end up with type MyClass = { myDate(): moment.Moment; }. You'd have to somehow expose these new getter and setter types as part of the interface, though I don't personally see why that would be desirable.

This issue is basically asking for a less-limited version of operator overloading of = and ===. (Getters and setters are already operator overloading.) And as someone who has dealt with operator overloads in other languages, I will say that I feel they should be avoided in most cases. Sure, someone could suggest some mathematical example like cartesian and polar points, but in a vast majority of cases where you'd use TS (including the examples in this thread), I would argue that needing operator overloading is probably a code smell. It might look like it's making the API simpler, but it tends to do the opposite. As mentioned earlier, having the following not be necessarily true is super confusing and unintuitive.

foo.x = y;
if (foo.x === y) { // this could be false?

(I think the only times I've seen setters used that seemed reasonable are for logging and for setting a "dirty" flag on objects so that something else can tell whether a field has been changed. Neither of these really changes the contract of the object.)

@MikeyBurkman

[...] but in a vast majority of cases where you'd use TS (including the examples in this thread), I would argue that needing operator overloading is probably a code smell [...]

 foo.x = y; 
 if (foo.x === y) { // this could be false?

So, merely having the types match does not prevent surprising behavior, and indeed el.style.display = ''; //so is this now true: el.style.display === ''? shows that that's not theoretical either. Even with plain old fields, the assumption doesn't hold for NaN, by the way.

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).

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.)

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

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 null, if value was not previously set, but cannot set it to null. Currently, you cannot do it, because setter input type must include null.

@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.

The homepage of typescriptlang.org is currently inaccurate when it states:

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.

TypeScript is a typed _superset_ of a _subset_ of JavaScript that compiles to plain JavaScript. :smiley:

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.

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);
    }
}

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.

constructor(value: Date | moment.Moment) {
    this.setMyDate(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.

@xhliu ...too complex to address...

https://ts-ast-viewer.com/#code/MYewdgzgLgBFCm0DyAjAVjAvDA3gKBkJgDMQQAuGAIhQEMAnKvAXzzwWXQDpSQg

const testObj = {
    foo: "bar"
}

testObj.foo

The symbol foo has this to say:

  foo
    flags: 4
    escapedName:"foo"
    declarations: [
      PropertyAssignment (foo)
    ]
    valueDeclaration: PropertyAssignment (foo)

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 accessDelclaration: AccessSpecification (foo), then the PropertyAccessExpression which is aware of the symbol foo and its declarations, could conditionally check if there is a "accessDelclaration" and use the typing from that instead.

Assuming the syntax exists to add that accessDelclaration property to the "foo" symbol, the PropertyAccessExpression should be able to pull the "AccessSpecification" from the symbol it gets from ts.createIdentifier("foo") and yield different types.

ts.createPropertyAccess(
  ts.createIdentifier("testObj"),
  ts.createIdentifier("foo")
)

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 ts.createPropertyAccess() function, and a Declaration class to represent that condition and its effects must be added to the symbol of the object property.

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 string to window.location didn't worked and I had to do as any workaround 😟

The Window.location read-only property returns a Location object with information about the current location of the document.

Though Window.location is a read-only Location object, you can also assign a DOMString to it. This means that you can work with location as if it were a string in most cases: location = 'http://www.example.com' is a synonym of location.href = 'http://www.example.com'.
source

migrating old JS code to TS where assigning string to window.location didn't worked and I had to do as any workaround

That's a great example.

TS needs this. This is a very normal part of JavaScript.

I see that the current issue was closed. But as I understand, this functionality was not realized?

@AGluk, this issue needs to be reopened.

Was this page helpful?
0 / 5 - 0 ratings