Repository: TypeScript
Issue: Allow "instance" private and protected class members that don't affect assignability
Labels: Suggestion,Awaiting More Feedback

# 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:
```ts
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

<!--
   Suggestions that don't meet all these criteria are very, very unlikely to be accepted.
   We always recommend reviewing the TypeScript design goals before investing time writing
   a proposal for ideas outside the scope of the project.
-->
My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).


## ⭐ Suggestion

Add an `instance` modifier that enforces that the member is only accessed with `this.`:

```ts
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:
```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:
```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:
```ts
export {getX} from 'foo';
```
`src/app.ts`:
```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&lt;T&gt; helper:

You can define a helper to get just the instance interface of a class like:

```ts
type Interface<T> = {
  [K in keyof T]: T[K];
}
```

But using it isn't trivial:

```ts
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.