microsoft / microsoft/TypeScript
Allow "instance" private and protected class members that don't affect assignability
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.4k
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 117
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
No source file, test, or compiler entry point is named. Start by reviewing the proposal's private and protected member assignability examples and the TypeScript design goals, then determine the affected type-checking rules and tests needed to define the new modifier's behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100