-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Different types based on visibility #43553
Comments
It sounds like you want #37487. Otherwise this proposal strikes me as really odd. How would this behave?
|
Not only do I want to make some properties be readonly when accessed publicly but I also want them to change their type. For example from Though those might sound like quite strict rules they are applicable to most common use cases (at least for me) where you want to secure the mutability of a property. |
@Jet132 For sure such constraints will have to be met.
@MartinJohns the example you wrote should result in an error because |
Although thinking about it more, there is something odd, see: class {
private prop: 'abc'
public prop: string
} In such a case, the public member can be assigned any string, violating the private |
If you look at my first comment I actually suggested only having the least visible type be mutable. I'll change the issue proposal to fit it |
I had missed it, good to mention it in the proposal. Another question is where would initialization be done? Only on the least visible type? Should the declaration order matter? class {
private prop = 'abc' as const
public readonly prop: string
} |
The initialization should be done in the least visible type and about the declaration order, I'm not sure but it should matter for readability. We can think of the more visible types like function overloads just for visibility. Would it then make sense to order it from most to least visible to have the initialization at the bottom? |
Sounds good to me at least. Like you said it would be similar to functions overloads. class {
public readonly prop: IThing
private prop: Thing = new Thing()
} Lastly, what would it mean to declare all 3 scopes? class {
public readonly prop: '?'
protected readonly prop: '??'
private prop: '???'
} |
TypeScript has no way to declare types as mutable / immutable. This would require something like #17181. |
If the ergonomics were improved for the cases where the value type doesn't change between access levels, then I would prefer this proposal over #37487. One suggestion, is if the value type was implicit for following declarations of the same property. class Foo {
public readonly prop: string;
private prop; // inferred as string
}
@Jet132 rather than requiring that only least visible is mutable which would really limit the usefulness of this proposal, what if types were constrained at mutation to use a intersection of all types across access levels. class Foo {
public prop: string;
private prop: 'abc';
}
const foo = new Foo();
foo.prop = 'abc'; // prop is constrained to ('abc' & string) which results in 'abc'
foo.prop // type is still string This would be helpful in handling non primitives as well. class Box <T> {
public readonly value: T;
private value;
constructor (value: T) {
this.value = value;
}
set (value: T) {
this.value = value;
};
}
interface ReadonlyBox <T> {
readonly value: T;
}
class Foo {
public prop: ReadonlyBox<string>;
private prop: Box<string> = new Box();
}
const foo = new Foo();
foo.prop = { value: 'abc' }; // TypeError because it doesn't satisfy (Box<string> & ReadonlyBox<string>) This eliminates the risk that prop could be replaced with a less capable object which would cause errors when a |
@MartinJohns I think we used the wrong terminology, we meant changing the value of the property. By "non-mutable" what we meant was "non-settable" as in prefixing with |
@robbiespeed this means leaking implementation details and breaking encapsulation? I am not in favor of this. Whenever you want to change implementation details, what is effectively used publicly might break. Although I'd be fine with the type inference. |
This would just make every scope have a different type. Note that the example above would throw an error because of the inclusive rule. class {
public readonly prop: '?' | '???';
protected readonly prop: '??' | '???';
private prop: '???';
}
@robbiespeed I agree with @marechal-p. The type should be explicitly set. It also prevents confusion of why public The second example doesn't make much sense as you are restricting the consumer of the class from modifying an object which it already has the modifyable instance of. What use would it be to restrict the user from modifying it via the property? There are no real usecases I can think of where this behavior would actually be needed. As I've mentioned in my first comment the rules do seem quite strict but in practice (at least in mine and those I can imagine) you won't be needing the features for anything more. Edit: and I'm also fine with type inference 👍 |
I've updated the proposal but without the type inference. There I'm not sure if it wouldn't be better to have the inference from bottom to top as you can only initialize it on the bottom. class Foo{
// Implicitly of type string
public readonly prop1;
private prop1: string;
// Implicitly of type number
public readonly prop2;
private prop2 = 1;
} This would make type inference even more useful though I'm not sure if typescript has an unwritten rule that type inference needs to be done from top-left to bottom-right. |
@Jet132 class Foo {
// Explicitly of type string
public readonly prop1;
private prop1: string;
// Implicitly of type number
public readonly prop2;
private prop2 = 1;
} compiles to: class Foo {
// Explicitly of type string
prop1;
prop1;
// Implicitly of type number
prop2;
prop2 = 1;
} whereas: class Foo {
// Explicitly of type string
declare public readonly prop1;
private prop1: string;
// Implicitly of type number
declare public readonly prop2;
private prop2 = 1;
} compiles to: class Foo {
// Explicitly of type string
prop1;
// Implicitly of type number
prop2 = 1;
} |
Well, the optimal implementation would not require the |
I think it would be best if order wasn't enforced, or instead of going from most to least visible it went from least to most. It seems clearer to me when initialization and/or type definition can come first. class Foo {
private prop = 1;
public readonly prop;
} |
hmm tbh, I would rather keep the order from most to least visible. It's more consistent with function overloads and also gives a visual indication of how the feature works. Like any not specified visibility just implicitly inherits that from the above (if there is one). class Foo {
public readonly prop;
// protected readonly prop;
private prop = 1;
} Maybe we should consider leaving out the type-inference to be even more consistent with function overloads and also with typescript's tendency to infer from left to right, top to bottom. |
Hate to necro this, but I'd really love to see this too. I'm running into Jet's exact problem with "dumb" getter/setters that shouldn't have to exist. Any plans for this yet? |
Suggestion
🔍 Search Terms
✅ Viability Checklist
My suggestion meets these guidelines:
⭐ Suggestion
Make properties (and getters/setters) be able to have different types based on where they are accessed from (AKA visibility).
It should have the following rules:
private
overprotected
overpublic
).📃 Motivating Example
It removes the need of using custom public/protected getters back by protected/private properties reducing a lot of clutter and repetitive "dumb" code when implementing a strictly typed API.
💻 Use Cases
What do you want to use this for?
Class APIs where you can see the inner state but may only mutate it through the provided method calls.
What workarounds are you using in the meantime?
Getters backed by properties prefixed with
_
.The text was updated successfully, but these errors were encountered: