Search code examples
typescriptreadonly

Why typescript allows referencing/assigning a readonly type/property by mutable type?


Why doesn't typescript enforce readonly keyword and prevent us from passing a read only property into non readonly one, that's defy the point

let foo: {
    readonly bar: number;
} = {
        bar: 123
    };

function iMutateFoo(foo: { bar: number }) {
    foo.bar = 456;
}

iMutateFoo(foo); // The foo argument is aliased by the foo parameter
console.log(foo.bar); // 456!```

Solution

  • It's a known behavior, tracked at microsoft/TypeScript#13347 whose surprising effects inspired an issue originally titled "readonly modifiers are a joke". The short answer to the question is "it would have broken backwards compatibility when readonly was introduced". The long answer comes from the following comment:

    @ahelsberg said:

    In order to ensure backwards compatibility, the readonly modifier doesn't affect subtype and assignability type relationships of the containing type (but of course it affects assignments to individual properties).

    Consider the following code:

    interface ArrayLike<T> {
      length: number;
      [index: number]: T;
    }
    
    function foo(array: ArrayLike<string>) {
       // Doesn't mutate array
    }
    
    var s = "hello";
    var a = ["one", "two", "three"];
    foo(s);  // s has readonly length and index signature
    foo(a);
    

    In existing TypeScript code there is no way to indicate if a particular property is intended to be read-only or mutable. In the code above, foo doesn't mutate the array it is passed, but there is nothing in the code that says it can't. However, now that we've added a readonly modifier to the length property and the index signature in the String interface (because they really are read-only), the foo(s) call above would be an error if we said that a readonly property is incompatible with a property without readonly. Specifically, we can't interpret the absence of a readonly modifier to mean read-write, we can only say that we don't know. So, if an interface differs from another interface only in the readonly modifiers on its properties, we have to say that the two interfaces are compatible. Anything else would be a massive breaking change.

    So there you have it. If you want to show your support for a fix to this, you might want to head over to that GitHub issue and give it a 👍 or describe your use case if it's compelling and not already mentioned.