TypeScript: Assignment operators allow assigning invalid values to literal types
Bug Report
🔎 Search Terms
addition assignment string literalappend string literal
🕗 Version & Regression Information
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about string literals
⏯ Playground Link
Playground link with relevant code
💻 Code
type Data = { value: 'literal' }
const data: Data = { value: 'literal' }
data.value += 'foo' // Appending a string is possible, despite it being a string literal type.
// data is still of type Data and can be passed along,
// even when the value has the wrong type.
useData(data);
function useData(_: Data): void {}
The same applies to any literal type (e.g. number). See #48857 for more examples.
🙁 Actual behavior
Appending a random string to the property typed as the literal "literal" is possible.
🙂 Expected behavior
I would expect an error, saying that the the type "literalfoo" is not assignable to "literal".
Ping @S0AndS0
About this issue
- Original URL
- State: open
- Created 3 years ago
- Reactions: 8
- Comments: 22 (5 by maintainers)
I would personally be very suspicious of any code that switched between members of a finite set of known string literals by way of mutation rather than direct assignment and wouldn’t mind the (hypothetical) error that disallowed it.
My opinion of this pattern for number literals is admittedly more fuzzy though, given that TS doesn’t have range types.
See also https://github.com/microsoft/TypeScript/issues/14745#issuecomment-459881335
This is a bit of a weird spot. Mutation of a value of a single literal type is clearly wrong, but also unlikely to be something you do accidently, so it’s a low-value error despite high confidence. However, mutation of a value of a union of literal types is likely to be correct in a way that we can’t statically verify, so it’s a false positive.
The natural way to split that would be to say that mutation of non-union literals is disallowed but mutation of union literals is “presumed OK”, but this is a subtyping violation because an operation that’s allowed on
T | Ushould also be valid on aTor aU.This code smells really bad, though, and it’d be nice to find a more reasonable solution.
OT: To whomever it may concern, please take my personal ensurances that OP understands what they are talking about.
Much confusion here shown in initial report.
I’m sure there are more specific doc mentions, but regarding objects this mentions:
That is, something like this is not protected and produces no errors:
Same doc mentions later that a strange construct like this will protect against property modification:
But there it is the entire object literal that is being declared constant (or actually the properties ‘readonly’)
In the second comment I was struck by:
Consider the difference between that and
There is no difference. Neither generates an error.
You have marked the literal string ‘first’ as constant. And every string literal is constant. You have said nothing about the variable
example_one. Changing that to:fails as expected because now the variable is marked as constant.
I’m afraid I see nothing surprising here (except the
{ url: "https://example.com", method: "GET" } as const;wow).Please consult the docs again. Each time more things make sense.
@tshinnic You missed the point of the issue by a staggering degree. There’s absolutely no confusion present, and I’m very aware that the object is mutable. The point is about the
+=operator allowing to assign incompatible types. Perhaps you should read the documentation about literal types.You also don’t seem to know what const contexts (aka
as const) do. Theas constwill narrow the type of the value, so the type will be"first", and notstring. Here’s an example that will hopefully illustrate the issue more:Or to make a more elaborate demonstration with objects using your example:
@S0AndS0 That would be
`prefix-${string}`:Oh good, a teachable moment awaits.