microsoft / microsoft/TypeScript
C++-style `const` modifier on class members
まだ誰も着手していません。
- 主要言語
- Go
- スター
- 111k
- フォーク
- 14.3k
- 平均マージ
- 1日 19時間
- マージ済み PR(30日)
- 117
説明
🔍 Search Terms
const method parameter keyword readonly class
C++ like const methods
class method with const keyword
"as const" for class type
✅ Viability Checklist
- 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 isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
⭐ Suggestion
I'm sure I've seen this suggestion somewhere before but I couldn't find it https://github.com/microsoft/TypeScript/issues/35313
Allow using as const on class objects by annotating methods as const (syntax tbd).
A const method would (like in C++) not allow modifying properties of this or calling any method not also marked as const.
An object marked with as const would similarly be readonly and only methods marked as const would be callable.
📃 Motivating Example
Keeping track of mutable vs immutable can be tricky when you're not used to the library you're using (or if its poorly written). It's very important to get it right in many libraries (react state, vue refs (shallow vs deep), preact/signals) where modifying values may or may not be required.
Debugging mutability bugs can be a pain sometimes too
const start = new Point(0, 0);
const line: Point[] = [];
for (let i = 0; i < 10; i++) {
line.push(start.add(i * 10, Math.random()));
}
// equivalent to
const line = new Array(10).fill(new Point(450, Math.random()));
Declaration syntax 1 using a const decoration
class Point {
constructor(public x: number, public y: number) { }
const clone(): Point { ... }
add(other: PointLike): this { ... }
}
const p = new Point(0, 0) as const; // type is { readonly x: number, readonly y: number, readonly clone(): Point } (`add` has been removed)
Declaration syntax 2 using this: const parameter
class Point {
constructor(public x: number, public y: number) { }
clone(this: const): Point { ... }
add(other: PointLike): this { ... }
}
const p = new Point(0, 0) as const; // type is { readonly x: number, readonly y: number, readonly clone(): Point } (`add` has been removed)
I personally much prefer the first syntax where const is a decoration.
Parameter syntax 1
function noModifyPoint(p: const Point) {
p.clone().add(1, 1);
}
function modifyPoint(p: Point) {
p.add(1, 1);
}
const p = new Point(0, 0) as const;
noModifyPoint(p); // ok
modifyPoint(p); // Argument of type `const Point` is not assignable to parameter of type `Point`. Type `const Point` is missing the following properties from type `Point`: add
Not to be confused with
function foo() {
const p = arguments[0];
//...
}
(as an aside a more appropriate syntax for that would be foo(const p: Point) or foo(const p: const Point) but that's OoS)
Parameter syntax 2
function noModifyPoint(p: Readonly<Point>) {
p.clone().add(1, 1);
}
function modifyPoint(p: Point) {
p.add(1, 1);
}
const p = new Point(0, 0) as const;
noModifyPoint(p); // ok
modifyPoint(p); // Argument of type `Readonly<Point>` is not assignable to parameter of type `Point`. Type `Readonly<Point>` is missing the following properties from type `Point`: add
Again I prefer syntax 1. The const type inference needs to happen in the compiler, making it look like a utility type would be confusing. The use of const also mirrors the declaration syntax 1.
The inference of the new readonly type should ™️ be simple enough, convert any r/w properties to readonly, and remove any functions not marked as const.
For simplicity arrow method properties should be handled as any other property would.
class Point {
constructor(public x: number, public y: number) { }
clone(this: const): Point { ... }
unsafelyModifyThis: () => { this.x = 10; }
}
const p = new Point(0, 0) as const;
p.unsafelyModifyThis(); // OK
The reason I suggest this is only because there needs to be a line drawn somewhere as to how complex this feature becomes. As mentioned in TypeScripts third non-goal this is a tradeoff between usefulness and simplicity.
Its already possible to break the type system and its up to the programmers to not do stupid things.
Other types
const a1 = [1, 2, 3] as const;
const a2 = [1, 2, 3] as Readonly<[number, number, number]>;
function aFunc1<T>(p: const Array<T>) {
// syntactically valid but is probably useful since Array doesn't have any built-in const decorations
// the resulting type would be `{ readonly [index: number]: T; readonly length: number }`
// also not sure how it would work with the `ReadonlyArray<T>` built-in type
...
}
function prim(n: const number) { ... } //error
💻 Use Cases
-
What do you want to use this for?
This would be most useful for data classes (aka structs) that represent non-primative data structures. -
What shortcomings exist with current approaches?
Workarounds are not DRY, requiring manual definition for const variants of all types -
What workarounds are you using in the meantime?
Manually defining aReadonlyPointvariation and casting to it.interface ReadonlyPoint { readonly x: number; readonly y: number; readonly xy: [number, number]; clone(): Point; } class Point implements ReadonlyPoint { constructor(public x: number, public y: number) { } clone(): Point { return new Point(this.x, this.y); } add(other: PointLike): this { this.x += other.x; this.y += other.y; return this; } //... } const p = new Point(0, 0) as ReadonlyPoint; p.add(p); // error
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
動機となる Point の例、提案されている const と Readonly の代替案、およびリンクされている以前の issue 35313 から始めます。完了とするには、実装とテストの範囲を決める前に、const メソッド、readonly クラスインスタンス、メソッド呼び出し、パラメーター、その他の型について、合意された構文とセマンティクスが必要です。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- typescript
- 領域
- compilers
- issue の種類
- 機能追加
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 活発さ
- 停滞
- 明瞭さ
- おおむね明確
- 初心者へのやさしさ
- 25/100