-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Description
Suggestion
Many classes use private and protected members for internal use only - that is, they always access them via this.field and never on other object with other.field.
For these classes it would be very useful to retain structural typing, instead of the effectively nominal typing you get with private and protected members currently.
This would be especially useful for avoiding compilation errors when programs have multiple copies of the same package installed (for the usual npm reasons) with classes that are actually compatible with each other, but aren't assignable according to TypeScript.
Could we add an instance modifier to class members that restricts the fields to only being access with this.?
Example:
class A {
instance private _x = 0;
get x() { return this._x; }
}
class B {
instance private _x = 0;
get x() { return this._x; }
}
const f = (a: A) => {}
// Should not be an error
f(new B());🔍 Search Terms
instance private assignable
✅ Viability Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
⭐ Suggestion
Add an instance modifier that enforces that the member is only accessed with this.:
class A {
instance private _x = 0;
get x() {
// fine
return this._x;
}
getOtherX(o: A) {
// error
return o._x;
}
}📃 Motivating Example
Besides the common case using a class type as an interface, it's far too easy to end up in situations where users have multiple copies of a package and pass objects from one copy to another:
node_modules/foo/index.ts:
export class A {
instance private _x = 0;
get x() { return this._x; }
}
export const getX = (a: A) => a.x;node_modules/bar/node_modules/foo/index.ts:
export class A {
instance private _x = 0;
get x() { return this._x; }
}
export const getX = (a: A) => a.x;node_modules/bar/index.ts:
export {getX} from 'foo';src/app.ts:
import {A} from 'foo';
import {getX} from 'bar';
// This causes a compile error but no runtime error:
getX(new A());💻 Use Cases
Workarounds
There are some workarounds, but they are finicky and don't work completely.
Interface<T> helper:
You can define a helper to get just the instance interface of a class like:
type Interface<T> = {
[K in keyof T]: T[K];
}But using it isn't trivial:
export class AImpl {
private _x = 0;
get x() { return this._x; }
}
export const getX = (a: A) => a.x;
export type A = Interface<AImpl>;
export const A = AImpl;Unfortunately, the exported type A doesn't fully represent the public interface of class AImpl. It doesn't have the static interface and it doesn't have the protected members which may be necessary for subclasses (and can still follow the rule that they are accessed only on the this instance).
/** @internal */
You can mark private members as /** @internal */ to exclude them from the published public types. This does not work for protected members though.