Assigning to a wider union type breaks type safety #55451
Description
🔎 Search Terms
invalid, type safety, union, type, broken type, no error
🕗 Version & Regression Information
- This is an invalid type checker behaviour
- This never changed, it was "always" like that >3.3.3
- This is the behavior in every version I tried, and I reviewed the FAQ for entries about 3.3, 3.5, 4.5, 5.0, 5.2.0-beta
⏯ Playground Link
💻 Code
type NumberOnlyContainer = {
prop: number;
}
let anything: {
prop: string | number;
};
let mod : NumberOnlyContainer = {prop: 1};
anything = mod; //it should fail here: "mod not assignable to anything because (string | number) is not assignable to number"
anything.prop = "definetly not a number"; //or it should narrow the "actual type" to {prop: number} and fail here: "anything.prop is narrowed to number, and string is not assignable to number"
console.log("a number : "+mod.prop); //in no case should mod.prop be able to not be a numberThe result will allways be a number : definetly not a number which is a violation to the types since mod should be of type NumberOnlyContainer which's prop should only be able to be a number
🙁 Actual behavior
The code compiles / ts does not complain
🙂 Expected behavior
The code should no "compile/transpile" since it is invalid and breaks type safety.
It should not be possible to effectively expand/redefine a variable by assigning its value to another mutable variable.
The only way where this behaviour should be allowed is if the wider type ((string|number)[] for example) is readonly since you cannot break type safety with it.
Options to prevent this:
- Disallow assigning the value of a variable of type
Ato a variable defined as typeBifA&Bare reference typesBis mutable (anything except areadonlyarray)Bis a "wider" type thanA
- Narrow down/merge the type of a variable of type
B(down toA) when a value of typeAis assigned to it - (currently not possible since
readonlyonly works for arrays) A variable of typeBis narrowed toreadonly Bwhen a value of typeAis assigned to it and:A&Bare reference typesBis mutable (anything except areadonlyarray)Bis a "wider" type thanA
All of these solutions would work for all the use cases (when considering passing a var to a function as a variable assignment)
Additional information about the issue
There are a lot more examples where this missing check should fail:
- https://www.typescriptlang.org/play?#code/DYUwLgBAtgngcgVygIxAJwM4QFwQHZKpoDaAuhALwTECMpA3AFCMBmCeAxmAJYD2eEXgDd0AdzTcwIAOqSAFgGUwEvAHMMACgy8oIAIJo0AQxi4jeGGQCUAb0YQI23QeOWADOSoAiPV6YBfZmExCSlZMEVlbjVNWEQUdAwrJkYOfm1QADpgXlUNMBgABxBeFmh4QkTiD2SgA
- https://www.typescriptlang.org/play?#code/DYUwLgBAtgngcgVygIxAJwM4QFwQHZKpoDaAuhALwTECMpA3AFCiQYD2UIAgmmgIYxcACgIp0AHwxg0ASzwBzAJRkm7Tj34xK0eIXQZVHbrwHEADOSoAiPG0h98etDkVWmAYzZ52oAHTA2eSFYRDFMRQ8vHxB-QKEwGAAHEDYAMx1QogxzUgigA
Since this behaviour (or rather the lack thereof) is pretty severe, I would be surprised to see that this is not a duplicate - that said, i do not found a matching issue. I guess it has already been reported or is at least known. If so - link the issue and close this one, maybe i am just too small brained to find the correct issue.
Activity
RyanCavanaugh commentedon Aug 21, 2023
Properties are assumed to be covariant (i.e. only read from); writes to supertype aliases can be unsound. We don't really have intent to try to "fix" this; it's an accepted unsoundness.
stafyniaksacha commentedon Aug 22, 2023
I've made another example, which I think it's related (let me know if it's not):
typescript-bot commentedon Aug 25, 2023
This issue has been marked as "Working as Intended" and has seen no recent activity. It has been automatically closed for house-keeping purposes.