Note [Instantiating field types in stock deriving]
Figuring out what the types of data constructor fields are in `deriving` can be surprisingly tricky. Here are some examples (adapted from #20375) to set the scene: data Ta = MkTa Int# data Tb (x :: TYPE IntRep) = MkTb x deriving instance Eq Ta -- 1. deriving instance Eq (Tb a) -- 2. deriving instance Eq (Tb Int#) -- 3. Example (1) is accepted, as `deriving Eq` has a special case for fields of type Int#. Example (2) is rejected, however, as the special case for Int# does not extend to all types of kind (TYPE IntRep). Example (3) ought to typecheck. If you instantiate the field of type `x` in MkTb to be Int#, then `deriving Eq` is capable of handling that. We must be careful, however. If we naïvely use, say, `dataConOrigArgTys` to retrieve the field types, then we would get `b`, which `deriving Eq` would reject. In order to handle `deriving Eq` (and, more generally, any stock deriving strategy) correctly, we /must/ instantiate the field types as needed. Not doing so led to #20375 and #20387. In fact, we end up needing to instantiate the field types in quite a few places: * When performing validity checks for stock deriving strategies (e.g., in GHC.Tc.Deriv.Utils.cond_stdOK) * When inferring the instance context in GHC.Tc.Deriv.Infer.inferConstraintStock * When generating code for stock-derived instances in GHC.Tc.Deriv.{Functor,Generate,Generics} Repeatedly performing these instantiations in multiple places would be wasteful, so we build a cache of data constructor field instantiations in the `dit_dc_inst_arg_env` field of DerivInstTys. Specifically: 1. When beginning to generate code for a stock-derived instance `T arg_1 ... arg_n`, the `dit_dc_inst_arg_env` field is created by taking each data constructor `dc`, instantiating its field types with `dataConInstUnivs dc [arg_1, ..., arg_n]`, and mapping `dc` to the instantiated field types in the cache. The `buildDataConInstArgEnv` function is responsible for orchestrating this. 2. When a part of the code in GHC.Tc.Deriv.* needs to look up the field types, we deliberately avoid using `dataConOrigArgTys`. Instead, we use `derivDataConInstArgTys`, which looks up a DataCon's instantiated field types in the cache. StandaloneDeriving is one way for the field types to become instantiated. Another way is by deriving Functor and related classes, as chronicled in Note [Inferring the instance context] in GHC.Tc.Deriv.Infer. Here is one such example: newtype Compose (f :: k -> Type) (g :: j -> k) (a :: j) = Compose (f (g a)) deriving Generic1 This ultimately generates the following instance: instance forall (f :: Type -> Type) (g :: j -> Type). Functor f => Generic1 (Compose f g) where ... Note that because of the inferred `Functor f` constraint, `k` was instantiated to be `Type`. GHC's deriving machinery doesn't realize this until it performs constraint inference (in GHC.Tc.Deriv.Infer.inferConstraintsStock), however, which is *after* the initial DerivInstTys has been created. As a result, the `dit_dc_inst_arg_env` field might need to be updated after constraint inference, as the inferred constraints might instantiate the field types further. This is accomplished by way of `substDerivInstTys`, which substitutes all of the fields in a `DerivInstTys`, including the `dit_dc_inst_arg_env`. It is important to do this in inferConstraintsStock, as the deriving/should_compile/T20387 test case will not compile otherwise.
References 1
- Inferring the instance context GHC.Tc.Deriv.Infer
Referenced by 6
- GHC.Tc.Deriv.Generate call site ×4
- GHC.Core.DataCon call site
- Generating a correctly typed Rep instance GHC.Tc.Deriv.Generics