Note [Record Updates]
To typecheck a record update, we expand it first. Suppose we have
data T p q = T1 { x :: Int, y :: Bool, z :: Char }
| T2 { v :: Char }
| T3 { x :: Int }
| T4 { p :: Float, y :: Bool, x :: Int }
| T5
Then the record update `e { x=e1, y=e2 }` expands as follows
e { x=e1, y=e2 }
===>
let { x' = e1; y' = e2 } in
case e of
T1 _ _ z -> T1 x' y' z
T4 p _ _ -> T4 p y' x'
T2, T3 and T5 should not occur, so we omit them from the match.
The critical part of expansion is to identify T and then T1/T4.
Wrinkle [Disambiguating fields]
As explained in Note [Disambiguating record updates] in GHC.Rename.Pat,
to typecheck a record update we first need to disambiguate the field labels,
in order to find a parent which has at least one constructor with all of the fields
being updated.
As mentioned in Note [Type-directed record disambiguation], we sometimes use
type-directed disambiguation, although this mechanism is deprecated and
scheduled for removal via the implementation of GHC proposal #366
https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0366-no-ambiguous-field-access.rst.
All in all, this means that when typechecking a record update via expansion,
we take the following steps:
(0) Perform a first typechecking pass on the record expression (`e` in the example above),
to infer the type of the record being updated.
(1) Disambiguate the record fields (potentially using the type obtained in (0)).
(2) Expand the record update as described above, using an XXExprGhcRn.
(a) Create a let-binding to share the record update right-hand sides.
(b) Expand the record update to a case expression updating all the
relevant constructors (those that have all of the fields being updated).
(3) Typecheck the expanded code.
In (0), we call inferRho to infer the type of the record being updated. This returns the
inferred type of the record, together with a typechecked expression (of type HsExpr GhcTc)
and a collection of residual constraints.
We have no need for the latter two, because we will typecheck again in (D3). So, for
the time being (and until GHC proposal #366 is implemented), we simply drop them.
Wrinkle [Using IdSig]
As noted above, we want to let-bind the updated fields to avoid code duplication:
let { x' = e1; y' = e2 } in
case e of
T1 _ _ z -> T1 x' y' z
T4 p _ _ -> T4 p y' x'
However, doing so in a naive way would cause difficulties for type inference.
For example:
data R b = MkR { f :: (forall a. a -> a) -> (Int,b), c :: Int }
foo r = r { f = \ k -> (k 3, k 'x') }
If we expand to:
ds_foo r =
let f' = \ k -> (k 3, k 'x')
in case r of
MkR _ b -> MkR f' b
then we are unable to infer an appropriately polymorphic type for f', because we
never infer higher-rank types. To circumvent this problem, we proceed as follows:
1. Obtain general field types by instantiating any of the constructors
that contain all the necessary fields. (Note that the field type must be
identical across different constructors of a given data constructor).
2. Let-bind an 'IdSig' with this type. This amounts to giving the let-bound
'Id's a partial type signature.
In the above example, it's as if we wrote:
ds_foo r =
let f' :: (forall a. a -> a) -> (Int, _b)
f' = \ k -> (k 3, k 'x')
in case r of
MkR _ b -> MkR f' b
This allows us to compute the right type for f', and thus accept this record update. References 2
- Disambiguating record updates GHC.Rename.Pat
- Type-directed record disambiguation GHC.Tc.Gen.Expr
Referenced by 11
- GHC.Tc.Gen.Expr call site ×7
- Handling overloaded and rebindable constructs GHC.Rename.Expr
- Doing XXExprGhcRn in the Renamer vs Typechecker GHC.Rename.Expr
- Unifying result types in tcRecordUpd GHC.Tc.Gen.Expr
- Generated code and pattern-match checking GHC.Types.Basic