DerivingVia does not typecheck the via type when deriving an empty list of classes
To my surprise, GHC accepts the following nonsense:
{-# LANGUAGE DerivingVia #-}
module Bug where
data Foo deriving () via Maybe Maybe
Maybe Maybe
clearly does not kind-check, so why is GHC allowing this garbage to pass through? The answer lies in TcDeriv.makeDerivSpecs
:
makeDerivSpecs is_boot deriv_infos deriv_decls
= do { -- We carefully set up uses of recoverM to minimize error message
-- cascades. See Note [Flattening deriving clauses].
; eqns1 <- sequenceA
[ recoverM (pure Nothing)
(deriveClause rep_tc (fmap unLoc dcs)
pred err_ctxt)
| DerivInfo { di_rep_tc = rep_tc, di_clauses = clauses
, di_ctxt = err_ctxt } <- deriv_infos
, L _ (HsDerivingClause { deriv_clause_strategy = dcs
, deriv_clause_tys = L _ preds })
<- clauses
, pred <- preds
]
If you squint, you'll notice that this calls deriveClause
—which, among other things, typechecks via
types—once per derived class. But if we provide an empty list of derived classes, like in deriving () via Maybe Maybe
, then we'll never call deriveClause
and, as a consequence, never kind-check Maybe Maybe
. Yikes.
In fact, this reveals another mild infelicity of DerivingVia
: GHC typechecks via
types far more often than it should. If one writes deriving (A1 t, ..., A20 t) via T t
, then GHC will typecheck T t
20 different times. This is incredibly wasteful since we really only need to check it once, and then use the typechecked T t
type to help typecheck A1 t
through A20 t
.
We should refactor the code in TcDeriv
to typecheck the via
type in each deriving
clause exactly once. This will fix both issues, since it will ensure that the via
type is always typechecked, even if the list of derived classes happens to be empty, and it will ensure that we don't typecheck the via
type more than is necessary.