Broadly speaking, the idea is that you should be able to create an accessor
decorator that doesn't expose a setter.
I think the most natural way to express this is for the set
function returned by an accessor decorator to have a never
return type. The idea is that such a decorator would result in the type system treating the resulting field as if it was a manually written getter without a setter.
Here's an example scenario:
import { Friend } from "hypothetical-lib";
const name: Friend<string> = new Friend();
class Person {
@name.readonly accessor person: string;
replace(other: string) {
name.set(this, other);
}
}
This mostly feels esoteric because you need to come up with patterns for internally accessing the private state.
The ideal syntax would probably be something like this:
class Person {
@readonly accessor #person: string;
replace(other: string) {
this.#person = other;
}
}
const person = new Person("Daniel");
person.name; // "Daniel"
It's pretty easy to understand why you'd want this, but the only way to accomplish this particular syntax using the current decorators would require manual mutation of the target. This would of course be opaque to TypeScript, so you'd probably end up with something like the above Friend
design, or perhaps something like:
import { Fields } from "hypothetical-lib";
const fields: Fields<{ name: string }> = Fields();
class Person {
@readonly(fields) accessor name;
@internal(fields) accessor #name;
}
In this case, you'd explicitly declare both #name
and name
, and use an externally declared object to coordinate between the two decorated properties.
You can implement something like this today in TypeScript, and it works quite well. Unfortunately, there's no way to communicate to TypeScript that the set
returned by the @readonly
decorator doesn't really exist.
function readonly<Class, T, K extends keyof T>(fields: Fields<T>):
// target and context have simplified types to avoid complicating the example
(target: Target<Class>, context: Context<Class> & { name: K }) =>
{ get: (this: Class) => value: T[K]; set: () => never };
/// ~~~~~
I tried to put some flesh on the motivation here, but it's really just about allowing accessor decorators to explicitly create an accessor without a setter. This is a pretty general goal with a lot of possible applications, and, afaict, it's not a terribly big delta from the existing design:
- TypeScript already knows how to treat a getter but no setter as a readonly property
- this design uses
=> never
in the return signature of the decorator to communicate the fact that there's no valid setter. This (a) doesn't change the JavaScript API, (b) correctly types the way you would have to implement this behavior in JavaScript, (c) intuitively matches other ways in which TypeScript usesnever
to drive control-flow-based inference.