microsoft / microsoft/TypeScript
Unsafe Map overload spoils direct property access on parenthesized assignment expression
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.3k
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 132
Description
### 🔎 Search Terms
map overload any unknown unsafe weakmap set binary assignment lazy init
### 🕗 Version & Regression Information
- This is the behavior in every version I tried
### ⏯ Playground Link
https://www.typescriptlang.org/play/?ts=5.9.0-dev.20250326#code/DYUwLgBAxghlAWIBcECyMAOAeAzmATgJYB2A5gDQQD2ARgFYhRgB8EAPhAK7EAmIAZiRA8A3ACgx-bk0JViEMCDwBGABQBrEAE8UeImQCUEAN5iI0OXgj4lnYJAC8EVbAQgIAfg9PiIAO5omKoGBgB0pOAa2gbi5gD0cebmAHoeZtbgnPjyNjh2YOIAvhJSxDJyCkpgAExROhB6JKRGpuauiJ7eEL4B6BjBsRbEVrn5EE7tIOGRmlox6QlJEKnpNmBZObb2RWJAA
### 💻 Code
```ts
let cache: Map | undefined;
function test1(key: string) {
const result = (cache ??= new Map()).get(key);
// ^? const result: any
return result;
}
function test2(key: string) {
cache ??= new Map();
const result = cache.get(key);
// ^? const result: object | undefined
return result;
}
```
### 🙁 Actual behavior
First `result` gets spoiled as `any`
### 🙂 Expected behavior
I'd expect both `result`s to be `object | undefined`
### Additional information about the issue
This bug reports an issue related to `Map` but, tbf, it's not unique to `Map` and a more general solution would have to be found to properly fix this. The problem is how `checkBinaryLikeExpressionWorker` create a union of left and right types here - without taking into consideration that the assignment target might already overlap with the right type. That union type gets reduced with `UnionReduction.Subtype` and `.get` gets read from it - instead of the assigned to reference on the left side.
Contributor guide
Research direction
Reproduce the difference between test1 and test2 using the linked TypeScript Playground, then inspect checkBinaryLikeExpressionWorker, which the report identifies as forming the union of the assignment operands. Trace how that union is reduced before .get is resolved. Done means the parenthesized assignment access infers object | undefined like the separate assignment form, with regression coverage for the reported example.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100