Note [Type-directed record disambiguation]

GHC/Tc/Gen/Expr.hs:1201 compiler 3 tickets

Deprecation notice:
  The type-directed disambiguation mechanism for record updates described in
  this Note is deprecated, as per GHC proposal #366 (https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0366-no-ambiguous-field-access.rst).
  The removal of type-directed disambiguation for record updates is tracked
  in GHC ticket #19461, but progress towards this goal has stalled.

  Why? There are several suggested replacement mechanisms, such as:
    1. using module qualification to disambiguate,
    2. using OverloadedRecordUpdate for type-directed disambiguation
      (as described in Note [Overview of record dot syntax] in GHC.Hs.Expr).
  However, these solutions do not work in all situations:
    1. Module qualification doesn't work for fields defined in the current module,
       nor to disambiguate between constructors of different data family instances
       of a given parent data family TyCon.
    2. OverloadedRecordUpdate does not allow for type-changing record update,
       nor can it deal with fields with existentials or polytypes.
  There are also some avenues to improve the renamer's ability to disambiguate:
    - GHC ticket #23032 suggests using as-patterns to disambiguate in the renamer.
    - GHC proposal https://github.com/ghc-proposals/ghc-proposals/pull/537
      suggests a syntactic form of type-directed disambiguation that could be
      carried out in the renamer.
  Neither of these have been accepted/implemented at the time of writing (Sept 2025).
  This means that removal of type-directed disambiguation is currently stalled.

GHC tries to disambiguate record updates in the renamer, as described in
Note [Disambiguating record updates] in GHC.Rename.Pat. However, if the renamer
is unable to disambiguate, the renamer will defer to the typechecker: see
GHC.Tc.Gen.Expr.disambiguateRecordBinds, and in particular the auxiliary
function identifyParentLabels, which picks a parent for the record update
using the following additional mechanisms:

  (a) Use the type being pushed in, if it is already a TyConApp. The
      following are valid updates at type `R`:

        g :: R -> R
        g x = x { fld1 = 3 }

        g' x = x { fld1 = 3 } :: R

  (b) Use the type signature of the record expression, if it exists and
      is a TyConApp. Thus this is valid update at type `R`:

        h x = (x :: R) { fld1 = 3 }

Note that this type-directed disambiguation mechanism isn't very robust,
as it doesn't properly integrate with the rest of the typechecker.
For example, the following updates will all be rejected as ambiguous:

    let r :: R
        r = blah
    in r { foo = 3 }

    \r. (r { foo = 3 }, r :: R)

Record updates which require constraint-solving should instead use the
-XOverloadedRecordUpdate extension, as described in Note [Overview of record dot syntax].

References 2

Referenced by 5