Closed
Description
TypeScript Version: 3.7.5
Search Terms: array, methods, readonly
Code
class Foo {
x(): void { }
notReadonly = 1;
}
function foo1(arg: Readonly<Foo>) {
arg.x = () => { }; // error ✅
arg.notReadonly = 2; // error ✅
}
function foo2(arg: readonly string[]) {
arg.length = 1; // error ✅
arg.forEach = () => { }; // no error ❌ - #22315
}
function foo3(arg: Readonly<ReadonlyArray<string[]>>) {
arg.length = 1; // error ✅
arg.forEach = () => { }; // no error ❌
}
interface MyArray<T> extends Array<T> {
}
function foo4(arg: MyArray<string>) {
arg.length = 1; // no error ✅
arg.forEach = () => { }; // no error ✅
}
function foo5(arg: Readonly<MyArray<string>>) {
arg.length = 1; // error ✅
arg.forEach = () => { }; // error ✅
}
Expected behavior:
ReadonlyArray<T>
and readonly T[]
methods should be immutable by default, or should be able to be marked as immutable via Readonly<T>
.
Actual behavior:
The typechecker has special handling under the hood which detects an array inside of Readonly<T>
converts it to the ReadonlyArray
type.
I.e.
Readonly<string[]> === readonly string[] ✅
Readonly<readonly string[]> === readonly string[] ❌
Readonly<Array<string>> === readonly string[] ✅
Readonly<ReadonlyArray<string>> === readonly string[] ❌
More Info:
- Methods are not readonly-by-default, and there's no direct way to declare a readonly method (Suggestion: readonly method #22315).
- Classes/objects can, however, have their methods marked as readonly via
Readonly<Class>
.
- Classes/objects can, however, have their methods marked as readonly via
- If you extend the array type (
interface MyArray<T> extends Array<T> {}
), then it works as expected, because the typechecker no longer thinks the type is an array type (checker.isArrayType(type) === false
). - I ran into this whilst building a rule which asserts that all function arguments are readonly (Rule proposal: Enforce read-only typescript-eslint/typescript-eslint#514).
Related Issues:
#22315