Note [Type-directed record disambiguation]
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
- Overview of record dot syntax GHC.Hs.Expr
- Disambiguating record updates GHC.Rename.Pat
Referenced by 5
- GHC.Tc.Gen.Expr call site ×3
- GHC.Rename.Pat call site
- Record Updates GHC.Tc.Gen.Expr