Semantic Diff

#9158 Treat a record spread that copies mutable fields as expansive

1 commit

8e26a9ca6922aaecfe526792eaf3d2f194cdffb9..pr-9158

fa279ba37 · cknitt + Claude · Nov 01, 2026, 07:05 UTC

Treat a record spread that copies mutable fields as expansive

Why

The value restriction checked only the fields a spread literal writes, so {...base, tag: 1} was non-expansive even when it copied a mutable field from base, and its type was generalized (#9156). A record literal allocates every field of its type, whether written, copied from the spread or left out, so the check now requires all of them to be immutable, with or without a spread.

addedDiagnostic record spread copying a mutable field

type cell<'a> = {tag: int, mutable value: option<'a>}@val external base: cell<'a> = "base"let copy = {...base, tag: 1}copy.value = Some(1)let s: option<string> = copy.value
diagnostic
Before
After
none; `s` holds the number 1 at run time This has type: option<int>But it's expected to have type: option<string> The incompatible parts: int vs string

Review

  • Does a spread of an immutable record still generalize? Yes; the super_errors fixture uses one at both option<int> and option<string> first.