inference: Model always-throwing `modifyfield!`/`replacefield!` as `Bottom` (#62235)
`setfield!_tfunc`, `swapfield!_tfunc` and `setfieldonce!_tfunc` already
return `Bottom` when the targeted field can never be written, namely a
`const` field or a field of an immutable type, since the underlying
store always throws. `modifyfield!_tfunc` and `replacefield!_tfunc` did
not perform that check and instead reported a `Pair`/cmpswap result
type, so an operation that can only ever throw was not recognized as
such.
Guard both tfuncs through `setfield!_tfunc`. `modifyfield!` stores
`op(o.f, v)` rather than `v`, so the simple tfunc only consults field
writability (passing `Any` as the value; the result is refined precisely
in `abstract_modifyop!`, the direct-call path that derives its return
type from `modifyfield!_tfunc`). `replacefield!` type-checks the
replacement `v` before the comparison and therefore throws even when the
comparison would have failed, so `v` is consulted as well — this also
models a replacement value that can never be stored into the field.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>