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
[feature] class properties that are "readonly in public, writable in private" or other permutations #37487
Some sort of syntax to describe readonly on the outside, writeable on the inside for a given property, so we can avoid making a getter just for this purpose (or using any of the convoluted options linked in that StackOverflow post).
changed the title [-][feature] syntax for class property that is "readonly on the outside, writable on the inside"[/-][+][feature] syntax for class properties that are "readonly on the outside, writable on the inside"[/+]on Mar 20, 2020
changed the title [-][feature] syntax for class properties that are "readonly on the outside, writable on the inside"[/-][+][feature] class properties that are "readonly on the outside, writable on the inside"[/+]on Mar 20, 2020
Just a note, but #2845 is similar but different. This one is not about making a getter/setter, but about making only a property, and being able to specify that the property be readonly on the outside of the class, and writable only inside the class (and subclasses too, depending on the keyword used)
Taking the idea a little further:
classFoo{// readonly in public code or subclasses, writable in this class onlypublicreadprivatewritefoo=123// readonly in public code, writable in this class or subclassespublicreadprotectedwritebar=123// not visible in public code, readonly in subclasses, writable in this class onlyprotectedreadprivatewritebaz=123}
and otherwise public is equivalent to publicread publicwrite, protected is equivalent to protectedread protectedwrite, and private is equivalent to privateread privatewrite, where the write specifiers can not be less strict than the read specifiers (or can they?).
Reacted by Michael Turkeev, Anatoly Belonog, Thomas Hudson, Sam Claus, George Messeha, zakcodez, Erik, Frank Weindel, weberc2, Jesper Borgstrup and 6 more
I'd love this feature in Typescript. It would allow me to simplify so many of my classes, especially in libraries and packages that expose a public API.
I'm not a huge fan of the publicread privatewrite syntax proposed by Joe Pea (@trusktr) as those keywords feel a little 'forced' to me. How about using the existing readonly keyword and allowing multiple access modifiers in increasing order of specificity (public > protected > private). Access is then resolved from right to left, with the first matching access modifier defining the access level and readonly status:
// Seen as `private foo` by class members// Seen as `public readonly foo` by other codepublicreadonlyprivate foo : Foo;// Seen as `private foo` by class members// Seen as `protected readonly foo` by subclass members// Not accessible by other code
protected readonlyprivatefoo : Foo;// Seen as `protected foo` by class and subclass members// Seen as `public readonly foo` by other codepublicreadonlyprotected foo : Foo;// Would technically be valid as well, though not very useful// Seen as `private readonly foo` by class members// Seen as `protected foo` by subclass members// Seen as `public readonly foo` by other codepublicreadonlyprotectedprivatereadonly foo : Foo;// Throws an error on compile, since the `private` modifier is more specific than `public`privatepublicreadonlyfoo;
The upside of this would be that we don't introduce new keywords, and the syntax is flexible enough in case other modifiers like readonly are added.
A downside would be that function signatures like these are a little harder to parse visually when reading the code. But then the same could be said for everything that adds more keywords.
A question here would be how to handle the 'no access modifier = public' rule that Typescript uses. Should the public modifier be required for signatures with multiple access modifiers, or would the following also be valid?
readonly privatefoo : Foo;
I'd argue that this could get confusing (was this intentional or did someone mix up the order of keywords?) so I think an explicit public modifier should be required.
Note: this is written by someone who has no knowledge of the Typescript compiler or language design in general. So I don't know how easy/difficult/impossible this would be to implement. I'm curious to get some feedback on the syntax and on why this is or isn't a good idea.
Reacted by Ash Clarke, hiraginoyuki, Andrey (Andrii) Los, Bradley Odell, Dave Houlbrooke, Erik, Leonardo Raele, Antoine Kleinpeter, Max Milton, Alvin Ramskogler and 2 more
Reacted by Griffork, Michael Turkeev, zakcodez, Arek Kukuła, Zachary White, Keerati Tansawatcharoen, matbee, Hermann Rolfes and melohagan
Just encountered a use case for this today when writing out a value ref class.
classRef<T>{readonlyvalue: T;constructor(value: T){this.value=value;}set(value: T){this.value=value;// errors unless I override the type of `this`returnvalue;}// ...}
Overriding the type of this, can get annoying fast if you have lots of methods that need write access.
There are other ways around it like storing the value in a private field then creating a getter, but that adds a performance penalty.
Joe Pea (@trusktr) It might be best to default reads to always be public.
// Seen as `private foo` by class members
// Seen as `public readonly foo` by other code
privatewrite foo: string
This goes along with how typescript works now, fields are by default public and you narrow their scope using private or protected. My guess is it would also be easier to accommodate most cases without the need to explicitly define both read and write.
Also here's how I am getting around this issue right now. It's a bit cumbersome, but maybe it will help out others who desire this feature.
typeWriteable<T>={-readonly[PinkeyofT]: T[P];};typeWritebaleRef<T>=Writeable<Ref<T>>;classRef<T>{readonlyvalue: T;constructor(value: T){this.value=value;}set(this: WritebaleRef<T>,value: T){this.value=value;// no error because `this` type is changed to WritebaleRef<T>returnvalue;}// ...}
This is one of the features I miss so much in TypeScript.
I don't really like the publicread privatewrite notation. Is there an other language that uses that notation?
Here are a few suggestions:
// Option 1: C# style
public name: string {get;privateset;}// Option 2: Swift styleprivate(set) name: string// Option 3: Swift struct-style
public readonlyname: stringmutatingchangeName(name: string){this.name=name}// Option 4: New keyword
public frozenname1: string
public readonlyname2: string
My choice would be the Swift style (option 1) or C# style (option 2). Option 3 is for if we can't change the property definition.
Reacted by Robbie Speed, Piers MacDonald, Ash Clarke, Griffork, Michael Turkeev, domske, Théo LUDWIG, Peter Matta, Anatoly Belonog, Denis Pshenov and 15 more
I also like the C# syntax but I think it's too far from the way getters and setters are defined in JS/TS so it would look out of place. Swift syntax looks nice though.
Reacted by Joe Pea, Piers MacDonald, Jonathan Sharman, Erik and PurkkaKoodari
With the new keywords I suggested we can do things like
publicwriteprotectedreadprivatewritefoo=123
which makes foo writable only in public and private code, but not in subclasses (it could be a valid use case, f.e. "I want this class to handle user input for foo, but I don't want subclasses to interfere with user input of foo, while the subclasses can still extend functionality in other areas").
Wouter Buckens (@woubuc) How would we specify the same thing with the space-separated existing keywords? Would it be
publicprotectedreadonlyprivatefoo=123
?
Also with the new keywords, order wouldn't matter (or at least, the intention is clear regardless of order):
privatewriteprotectedreadpublicwritefoo=123
Here's my attempt with the existing keywords:
privateprotectedreadonlypublicfoo=123
But with the new keywords, what would happen if someone tries to use the existing keywords?:
I'm not sure overly precise granularity is useful if it makes us write crazy things to define properties?
I think something simple like the following would do the trick:
classFoo{/** * foo can be read from public, protected and private contexts, * writable only within the class and its subclasses. */publicprotectedfoo: number;}classBar{/** * bar can be read from public, protected and private contexts, * writable only within the class. */publicprivatebar: number;}classBuzz{/** * buzz can be read from protected and private contexts, * writable only within the class. */protectedprivatebuzz: number;}
Adding "larger" modifiers would only expand readability, and writabiIity would be kept to the most restricted modifier. We can finally add the readonly keyword in this word soup to close this last scope and never be able able to write.
edit: Actually you would never need to mix the readonly keyword with more than one access modifier.
I don't really see a case where someone would want it the other way around, like "public read/write and protected readonly".
Reacted by 龙腾道, Thomas Hudson, The Reality Engineer, Gavin Golden, Derek Jones, Purpzie, Erik, Wralith and Joseph Merdrignac
Reacted by Griffork, Allan Juan, Denis Pshenov and Eino Gourdin
@marechal-p The example I showed above was "public read/write, protected readonly, private read/write". In that concept, this allows the public (end users) to pass data in, and only allows the private scope to have full control (subclasses aren't afforded this).
With your idea (which I do like as a subset and feel that it would already be a lot better than what we currently have), how could we implement similar? Seems impossible.
I think being able to distinguish public input from protected input (in the private scope) could be useful. But I would totally settle with your idea over the current.
I agree that the compound words are ugly but TS is based on JS / ES, not Swift, and has no other cases where a modifier uses function-like syntax similar to what you've linked. I believe that there are combinations of existing reserved words, in an order that is currently a syntax-error (and thus shouldn't be a breaking change!), which would suffice to implement this feature.
Reacted by Dmytro Demensky, Julian Schmidt, Jacob Avery and jackykwandesign
After going through the protectedwriteproposal (which already satisfies my needs) I think another possible future addition would be a privatewrite modifier as suggested by Robbie Speed (@robbiespeed).
These write modifiers play well with the standard access modifiers public, private, and protected and from my point of view are very easy to reason about.
classOrder{// Can write from this class only, read from anywhereprivatewriteprice: number;// Same as above, but explicit public accesspublicprivatewriteprice: number;// Accessable from this class and subclasses, writable only from this classprotectedprivatewriteprice: number;// Compiler/linter warning because privatewrite has no effectprivateprivatewriteprice: number;// Compiler error because readonly conflicts with privatewritereadonlyprivatewriteprice: number;}
The separate modifier overloads are collapsed together and tested against based on where the access is happening. Only one override can set a default value. Remove the duplicate identifier warning, but add a warning if overloads are not adjacent (same as method overloads).
Reacted by James Bromwell, Max Khokhryakov, Wouter Buckens, Moritz Hertler, Steven Nguyen, Wralith, Leonardo Raele, Savva Mikhalevski, Noire, Dave Houlbrooke and 8 more
I like this idea because it reinforces the notion that access modifiers, like function parameter types, are a type-space construct. I'm not clear on your Cat example though - what does it mean to have two protected declarations?
The separate modifier overloads are collapsed together and tested against based on where the access is happening. Only one override can set a default value. Remove the duplicate identifier warning, but add a warning if overloads are not adjacent (same as method overloads).
That would be like my suggestion at #43553 which also has the benefit of supporting stricter types for broader access scopes.
Reacted by Dave Houlbrooke, Turtlefight, Sviatoslav Zaytsev, Steven Nguyen and Robbie Speed
I like this idea because it reinforces the notion that access modifiers, like function parameter types, are a type-space construct. I'm not clear on your Cat example though - what does it mean to have two protected declarations?
Similar thoughts on this one. Regarding the Cat example, it needs to be decided, if it has any relevance at all, or if it should rather be an error reported at compile time. The behavior is hard to predict (can the subclass now modify the value or not?), thus, I would rather suggest a compile-time error.
Suggestion for compile-time error: I think, as a rule of thumb, the readonly part must always have higher visibility than the writable part. Otherwise, a compile-error should be reported.
I think the best way to implement this is a possibility to add different types for each access modifiers.
This will be useful for mutable properties like arrays, map and sets.
//foo is readonly in public, but can be changed by this and derived classes.
public readonly foo : number;
protected foo : number;
private foo : number;
//other classes can't add items to `data`, but current class can.
public readonly data: ReadonlyMap<string, number>;
private data : Map<string, number>;
Reacted by Sviatoslav Zaytsev, Dave Houlbrooke and Savva Mikhalevski
// code from proposalclassC{accessora=1;// same as `accessor a { get; set; } = 1;`accessorb{}=1;// same as `accessor b { get; set; } = 1;`accessorc{get;set;}=1;// same as `accessor c = 1;`accessord{get;}=1;// getter but no setteraccessore{set;}=1;// setter but no getter (use case: decorators)accessorf{get; #set;};// getter with private setter `#f`;accessorg{ #set;}=1;// private setter but no getter (use case: decorators)accessor #h =1;// same as `accessor #h { get; set; } = 1;`accessor #i {}=1;// same as `accessor #i { get; set; } = 1;`accessor #j {get;set;}=1;// same as `accessor #j = 1;`accessor #k {get;}=1;// getter but no setteraccessor #l {set;}=1;// setter but no getter (use case: decorators)}
So, I suggest just add access modifiers into brackets like this:
Sviatoslav Zaytsev (@sviat9440) There are two options, it could work exactly as overloads on functions do right now, so you'd declare the implementation last (and independently) like this:
public readonlyfoo: ReadonlyMap<string,string>;
private foo: Map<string,string>;foo=newMap<string,string>();
Second option is to combine an overload signature and an implementation. This would be slightly different to how overload functions work now (but slightly more convenient), because if an overload signature exists the base signature of the function is not used outside the implementation.
public readonlyfoo: ReadonlyMap<string,string>;privatefoo: Map<string,string>=newMap();
The implementation should be last for style reasons, but it shouldn't be enforced (otherwise ESLint would probably generate an no-redeclare error but TS itself shouldn't care).
All signatures should be adjacent or TS generates a should be adjacent error.
Signatures must be compatible with the implementation or TS generates a must be compatible error.
Having a second implementation would make TS generate a no same property error as it does right now.
The separate modifier overloads are collapsed together and tested against based on where the access is happening. Only one override can set a default value. Remove the duplicate identifier warning, but add a warning if overloads are not adjacent (same as method overloads).
I like this as it's common to expose a sub-class publicly.
classExample{publicreadonlydoStuff()privatereadonlydoStuff(hiddenOption)doStuff(hiddenOption){// do stuff}}
This also has the benefit of not introducing new keywords.
Reacted by Robbie Speed, Roger Leung, Jack Waterfall, Artur Eshenbrener, Dave Houlbrooke, Patryk Miszczak, Sophie Kirschner, David Alexander Pfeiffer, Gavin H, shreyasm-dev and 20 more
Reacted by Artur Eshenbrener, Dave Houlbrooke, Gavin H, Max Khokhryakov, Adrian Romanowski, Chiri Vulpes and Ramrami Mohamed
Activity
[-][feature] syntax for class property that is "readonly on the outside, writable on the inside"[/-][+][feature] syntax for class properties that are "readonly on the outside, writable on the inside"[/+][-][feature] syntax for class properties that are "readonly on the outside, writable on the inside"[/-][+][feature] class properties that are "readonly on the outside, writable on the inside"[/+]RyanCavanaugh commentedon Mar 20, 2020
Previously at #2845 but we should revisit after so long
trusktr commentedon Mar 22, 2020
Just a note, but #2845 is similar but different. This one is not about making a getter/setter, but about making only a property, and being able to specify that the property be readonly on the outside of the class, and writable only inside the class (and subclasses too, depending on the keyword used)
Taking the idea a little further:
and otherwise
publicis equivalent topublicread publicwrite,protectedis equivalent toprotectedread protectedwrite, andprivateis equivalent toprivateread privatewrite, where thewritespecifiers can not be less strict than thereadspecifiers (or can they?).woubuc commentedon May 20, 2020
I'd love this feature in Typescript. It would allow me to simplify so many of my classes, especially in libraries and packages that expose a public API.
I'm not a huge fan of the
publicread privatewritesyntax proposed by Joe Pea (@trusktr) as those keywords feel a little 'forced' to me. How about using the existingreadonlykeyword and allowing multiple access modifiers in increasing order of specificity (public > protected > private). Access is then resolved from right to left, with the first matching access modifier defining the access level and readonly status:The upside of this would be that we don't introduce new keywords, and the syntax is flexible enough in case other modifiers like
readonlyare added.A downside would be that function signatures like these are a little harder to parse visually when reading the code. But then the same could be said for everything that adds more keywords.
A question here would be how to handle the 'no access modifier = public' rule that Typescript uses. Should the
publicmodifier be required for signatures with multiple access modifiers, or would the following also be valid?I'd argue that this could get confusing (was this intentional or did someone mix up the order of keywords?) so I think an explicit
publicmodifier should be required.Note: this is written by someone who has no knowledge of the Typescript compiler or language design in general. So I don't know how easy/difficult/impossible this would be to implement. I'm curious to get some feedback on the syntax and on why this is or isn't a good idea.
robbiespeed commentedon Jun 11, 2020
Just encountered a use case for this today when writing out a value ref class.
Overriding the type of
this, can get annoying fast if you have lots of methods that need write access.There are other ways around it like storing the value in a private field then creating a getter, but that adds a performance penalty.
Joe Pea (@trusktr) It might be best to default reads to always be public.
This goes along with how typescript works now, fields are by default public and you narrow their scope using private or protected. My guess is it would also be easier to accommodate most cases without the need to explicitly define both read and write.
Also here's how I am getting around this issue right now. It's a bit cumbersome, but maybe it will help out others who desire this feature.
yvbeek commentedon Jun 23, 2020
This is one of the features I miss so much in TypeScript.
I don't really like the
publicread privatewritenotation. Is there an other language that uses that notation?Here are a few suggestions:
My choice would be the Swift style (option 1) or C# style (option 2). Option 3 is for if we can't change the property definition.
woubuc commentedon Jun 24, 2020
I also like the C# syntax but I think it's too far from the way getters and setters are defined in JS/TS so it would look out of place. Swift syntax looks nice though.
trusktr commentedon Aug 4, 2020
Neat to know how other languages do this.
Wouter Buckens (@woubuc) Your idea is great if we need to prevent from adding new keywords.
With the new keywords I suggested we can do things like
which makes
foowritable only in public and private code, but not in subclasses (it could be a valid use case, f.e. "I want this class to handle user input for foo, but I don't want subclasses to interfere with user input of foo, while the subclasses can still extend functionality in other areas").Wouter Buckens (@woubuc) How would we specify the same thing with the space-separated existing keywords? Would it be
?
Also with the new keywords, order wouldn't matter (or at least, the intention is clear regardless of order):
Here's my attempt with the existing keywords:
But with the new keywords, what would happen if someone tries to use the existing keywords?:
Perhaps they'd need to be mutually exclusive, so we can either use only the existing keywords, or only the new ones, but not both.
whzx5byb commentedon Aug 17, 2020
The
Readonly<T>generic type (or an interface) will do the "public readonly, private writable" pattern.yvbeek commentedon Aug 21, 2020
whzx5byb I appreciate your example, but it is not a solution to the problem.
Fooand then set thefoovariablepaul-marechal commentedon Sep 21, 2020
I'm not sure overly precise granularity is useful if it makes us write crazy things to define properties?
I think something simple like the following would do the trick:
Adding "larger" modifiers would only expand readability, and writabiIity would be kept to the most restricted modifier.
We can finally add thereadonlykeyword in this word soup to close this last scope and never be able able to write.edit: Actually you would never need to mix the
readonlykeyword with more than one access modifier.I don't really see a case where someone would want it the other way around, like "public read/write and protected readonly".
trusktr commentedon Nov 3, 2020
@marechal-p The example I showed above was "public read/write, protected readonly, private read/write". In that concept, this allows the public (end users) to pass data in, and only allows the private scope to have full control (subclasses aren't afforded this).
With your idea (which I do like as a subset and feel that it would already be a lot better than what we currently have), how could we implement similar? Seems impossible.
I think being able to distinguish public input from protected input (in the private scope) could be useful. But I would totally settle with your idea over the current.
41 remaining items
thw0rted commentedon Mar 22, 2022
I agree that the compound words are ugly but TS is based on JS / ES, not Swift, and has no other cases where a modifier uses function-like syntax similar to what you've linked. I believe that there are combinations of existing reserved words, in an order that is currently a syntax-error (and thus shouldn't be a breaking change!), which would suffice to implement this feature.
ifeltsweet commentedon Jul 6, 2022
After going through the
protectedwriteproposal (which already satisfies my needs) I think another possible future addition would be aprivatewritemodifier as suggested by Robbie Speed (@robbiespeed).These write modifiers play well with the standard access modifiers
public,private, andprotectedand from my point of view are very easy to reason about.dhoulb commentedon Sep 2, 2022
Feel like this suggestion is blocked because of we haven't found a syntax people love, so throwing another option into the ring:
Overloading
Undo the requirement that members must have identical modifiers, and embrace the modifiers being different:
The separate modifier overloads are collapsed together and tested against based on where the access is happening. Only one override can set a default value. Remove the duplicate identifier warning, but add a warning if overloads are not adjacent (same as method overloads).
thw0rted commentedon Sep 2, 2022
I like this idea because it reinforces the notion that access modifiers, like function parameter types, are a type-space construct. I'm not clear on your Cat example though - what does it mean to have two
protecteddeclarations?Jet132 commentedon Sep 3, 2022
That would be like my suggestion at #43553 which also has the benefit of supporting stricter types for broader access scopes.
mmichaelis commentedon Sep 3, 2022
Similar thoughts on this one. Regarding the
Catexample, it needs to be decided, if it has any relevance at all, or if it should rather be an error reported at compile time. The behavior is hard to predict (can the subclass now modify the value or not?), thus, I would rather suggest a compile-time error.Suggestion for compile-time error: I think, as a rule of thumb, the
readonlypart must always have higher visibility than the writable part. Otherwise, a compile-error should be reported.dhoulb commentedon Sep 3, 2022
Thanks! That was a typo. Second one should've been
private. I've updated the code.khokm commentedon Oct 17, 2022
I think the best way to implement this is a possibility to add different types for each access modifiers.
This will be useful for mutable properties like arrays, map and sets.
sviat9440 commentedon Oct 18, 2022
If property has an initializer, where should I pass it in your example?
khokm commentedon Oct 18, 2022
I think it shouldn't be matter, just use one of them.
zkulbeda commentedon Nov 3, 2022
Hi. There is a tc39/proposal-grouped-and-auto-accessors that suggested syntax for read-only field, but using same syntax as ECMAScript private fields. This will be compatible with ECMAScript decorators currently being worked on for TS 5.0 #48885
So, I suggest just add access modifiers into brackets like this:
snarfblam commentedon Nov 3, 2022
That looks great as far as accessor properties are concerned but this thread is largely concerned with data properties.
dhoulb commentedon Nov 21, 2022
Sviatoslav Zaytsev (@sviat9440) There are two options, it could work exactly as overloads on functions do right now, so you'd declare the implementation last (and independently) like this:
Second option is to combine an overload signature and an implementation. This would be slightly different to how overload functions work now (but slightly more convenient), because if an overload signature exists the base signature of the function is not used outside the implementation.
The implementation should be last for style reasons, but it shouldn't be enforced (otherwise ESLint would probably generate an
no-redeclareerror but TS itself shouldn't care).All signatures should be adjacent or TS generates a
should be adjacenterror.Signatures must be compatible with the implementation or TS generates a
must be compatibleerror.Having a second implementation would make TS generate a
no same propertyerror as it does right now.LongTengDao commentedon Dec 9, 2022
Dave Houlbrooke (@dhoulb) go idea
I think force add
declarekeyword would be more safe:At least forbid to write assignment not at last to avoid:
being transformed by old type eraser util to:
which cause runtime
1disappear bug.TheCymaera commentedon Apr 9, 2023
I like this as it's common to expose a sub-class publicly.
Also lets us hide function parameters:
This also has the benefit of not introducing new keywords.