You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A class can implement an interface with only a getter for a non-read-only fieldΒ #49345
interfaceX{field: number;}classYimplementsX{getfield(){return1};// set field(x: number) {}}// this generates an error: "cannot assign to field because it is a read-only property"consty: Y={field: 1};y.field=3;// but this is fineconstx: X=y;x.field=2;
π Actual behavior
A class is considered to implement an interface when it includes only a getter for that interface's field; no error is generated.
π Expected behavior
Instances of this class can't be substituted for instances of this interface, so the class definition should generate an error.
Since the interface implies that one can both get and set field, the class should be considered incomplete without both a getter and setter for field. Given a member of the interface, setting field typechecks, but given a member of the class, it throws an error.
The example is a minimal repro from a real bug we found that followed this pattern, and we were surprised that it was allowed.
Not saying this isn't a bug, but note that in general TypeScript's readonly guarantees aren't very strong and the type system largely acts as if objects were passed by value, so this issue is just scratching the surface of the tip of that iceberg:
interfaceX{field: 1|2;}interfaceY{readonlyfield: 1|2;}interfaceZ{field: number;}// warning: badnessconsty: Y={field: 1};constx: X=y;// obj with readonly field assigned to one with writable fieldx.field=2;console.log(y.field);// readonly; assigned value was 1; prints 2// and if that weren't bad enough...constz: Z=y;// obj with narrow readonly field assigned to one with wide writable fieldz.field=42;console.log(y.field);// type is 1 | 2; readonly; assigned value was 1; prints 42(!)
Your issue is in fact a symptom of the above: Y is assignable to X (getter-only accessors are equivalent to readonly in the type system), so is considered a valid implementation of the interface.
Reacted by Martin Johns, Ryan Cavanaugh and Sean Alunni
A useful mental model is that properties are "read-only" from the perspective of how the type system treats them, but allows writes anyway unless they're marked readonly. There are any number of soundness holes as a result (subtyping being the obvious one, and indeed you can think of field as having a setter of type never).
Just so I'm clear - is this a known limitation that potentially will be fixed in the future, or a known limitation that we don't want to do anything about?
The problem is social, not technical. This is easily "fixable" in principle by adding a --strictReadonly flag (maybe a few dozen lines of code at most), or a strict property variance flag, but this setting wouldn't be usable until everyone updated tens of thousands of definition files to clarify whether any given property is supposed to be mutable or not. There's just not a lot of appetite for forcing literally everyone who uses TS to start thinking about that problem. So I don't think it's likely to happen any time soon, if ever.
A useful mental model is that properties are "read-only" from the perspective of how the type system treats them, but allows writes anyway unless they're marked readonly.
I prefer to think of it more as the type system assuming pass-by-value semantics (i.e. assignment implies a copy is made), as this is the only way I can justify { readonly x: "foo" | "bar" } -> { x: string } being a legal assignment. And to be fair this does accurately model most POJO scenarios, but kind of falls apart when used to model inheritance.
Activity
fatcerberus commentedon Jun 2, 2022
Not saying this isn't a bug, but note that in general TypeScript's readonly guarantees aren't very strong and the type system largely acts as if objects were passed by value, so this issue is just scratching the surface of the tip of that iceberg:
Your issue is in fact a symptom of the above:
Yis assignable toX(getter-only accessors are equivalent toreadonlyin the type system), so is considered a valid implementation of the interface.RyanCavanaugh commentedon Jun 2, 2022
A useful mental model is that properties are "read-only" from the perspective of how the type system treats them, but allows writes anyway unless they're marked
readonly. There are any number of soundness holes as a result (subtyping being the obvious one, and indeed you can think offieldas having a setter of typenever).bendmorris commentedon Jun 2, 2022
Just so I'm clear - is this a known limitation that potentially will be fixed in the future, or a known limitation that we don't want to do anything about?
RyanCavanaugh commentedon Jun 2, 2022
The problem is social, not technical. This is easily "fixable" in principle by adding a
--strictReadonlyflag (maybe a few dozen lines of code at most), or a strict property variance flag, but this setting wouldn't be usable until everyone updated tens of thousands of definition files to clarify whether any given property is supposed to be mutable or not. There's just not a lot of appetite for forcing literally everyone who uses TS to start thinking about that problem. So I don't think it's likely to happen any time soon, if ever.fatcerberus commentedon Jun 2, 2022
I prefer to think of it more as the type system assuming pass-by-value semantics (i.e. assignment implies a copy is made), as this is the only way I can justify
{ readonly x: "foo" | "bar" } -> { x: string }being a legal assignment. And to be fair this does accurately model most POJO scenarios, but kind of falls apart when used to model inheritance.typescript-bot commentedon Jun 4, 2022
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.