diff options
author | Ryan Scott <ryan.gl.scott@gmail.com> | 2018-06-04 21:20:02 -0400 |
---|---|---|
committer | Ben Gamari <ben@smart-cactus.org> | 2018-06-04 22:37:19 -0400 |
commit | 8ed8b037fee9611b1c4ef49adb6cf50bbd929a27 (patch) | |
tree | ff3907f0412085a78e694597c1bdba700740403f /compiler/rename/RnSource.hs | |
parent | 85309a3cda367425cca727dfa45e5e6c63b47391 (diff) | |
download | haskell-8ed8b037fee9611b1c4ef49adb6cf50bbd929a27.tar.gz |
Introduce DerivingVia
This implements the `DerivingVia` proposal put forth in
https://github.com/ghc-proposals/ghc-proposals/pull/120.
This introduces the `DerivingVia` deriving strategy. This is a
generalization of `GeneralizedNewtypeDeriving` that permits the user
to specify the type to `coerce` from.
The major change in this patch is the introduction of the
`ViaStrategy` constructor to `DerivStrategy`, which takes a type
as a field. As a result, `DerivStrategy` is no longer a simple
enumeration type, but rather something that must be renamed and
typechecked. The process by which this is done is explained more
thoroughly in section 3 of this paper
( https://www.kosmikus.org/DerivingVia/deriving-via-paper.pdf ),
although I have inlined the relevant parts into Notes where possible.
There are some knock-on changes as well. I took the opportunity to
do some refactoring of code in `TcDeriv`, especially the
`mkNewTypeEqn` function, since it was bundling all of the logic for
(1) deriving instances for newtypes and
(2) `GeneralizedNewtypeDeriving`
into one huge broth. `DerivingVia` reuses much of part (2), so that
was factored out as much as possible.
Bumps the Haddock submodule.
Test Plan: ./validate
Reviewers: simonpj, bgamari, goldfire, alanz
Subscribers: alanz, goldfire, rwbarton, thomie, mpickering, carter
GHC Trac Issues: #15178
Differential Revision: https://phabricator.haskell.org/D4684
Diffstat (limited to 'compiler/rename/RnSource.hs')
-rw-r--r-- | compiler/rename/RnSource.hs | 158 |
1 files changed, 136 insertions, 22 deletions
diff --git a/compiler/rename/RnSource.hs b/compiler/rename/RnSource.hs index 5e01f285b4..a53adf2cba 100644 --- a/compiler/rename/RnSource.hs +++ b/compiler/rename/RnSource.hs @@ -51,7 +51,7 @@ import NameEnv import Avail import Outputable import Bag -import BasicTypes ( DerivStrategy, RuleName, pprRuleName ) +import BasicTypes ( RuleName, pprRuleName ) import FastString import SrcLoc import DynFlags @@ -68,7 +68,6 @@ import Control.Arrow ( first ) import Data.List ( mapAccumL ) import qualified Data.List.NonEmpty as NE import Data.List.NonEmpty ( NonEmpty(..) ) -import Data.Maybe ( isJust ) import qualified Data.Set as Set ( difference, fromList, toList, null ) {- | @rnSourceDecl@ "renames" declarations. @@ -956,14 +955,16 @@ Here 'k' is in scope in the kind signature, just like 'x'. -} rnSrcDerivDecl :: DerivDecl GhcPs -> RnM (DerivDecl GhcRn, FreeVars) -rnSrcDerivDecl (DerivDecl _ ty deriv_strat overlap) +rnSrcDerivDecl (DerivDecl _ ty mds overlap) = do { standalone_deriv_ok <- xoptM LangExt.StandaloneDeriving - ; deriv_strats_ok <- xoptM LangExt.DerivingStrategies ; unless standalone_deriv_ok (addErr standaloneDerivErr) - ; failIfTc (isJust deriv_strat && not deriv_strats_ok) $ - illegalDerivStrategyErr $ fmap unLoc deriv_strat - ; (ty', fvs) <- rnHsSigWcType DerivDeclCtx ty - ; return (DerivDecl noExt ty' deriv_strat overlap, fvs) } + ; (mds', ty', fvs) + <- rnLDerivStrategy DerivDeclCtx mds $ \strat_tvs ppr_via_ty -> + rnAndReportFloatingViaTvs strat_tvs loc ppr_via_ty "instance" $ + rnHsSigWcType DerivDeclCtx ty + ; return (DerivDecl noExt ty' mds' overlap, fvs) } + where + loc = getLoc $ hsib_body $ hswc_body ty rnSrcDerivDecl (XDerivDecl _) = panic "rnSrcDerivDecl" standaloneDerivErr :: SDoc @@ -1632,35 +1633,148 @@ rnDataDefn doc (HsDataDefn { dd_ND = new_or_data, dd_cType = cType = do { deriv_strats_ok <- xoptM LangExt.DerivingStrategies ; failIfTc (lengthExceeds ds 1 && not deriv_strats_ok) multipleDerivClausesErr - ; (ds', fvs) <- mapFvRn (rnLHsDerivingClause deriv_strats_ok doc) ds + ; (ds', fvs) <- mapFvRn (rnLHsDerivingClause doc) ds ; return (L loc ds', fvs) } rnDataDefn _ (XHsDataDefn _) = panic "rnDataDefn" -rnLHsDerivingClause :: Bool -> HsDocContext -> LHsDerivingClause GhcPs +rnLHsDerivingClause :: HsDocContext -> LHsDerivingClause GhcPs -> RnM (LHsDerivingClause GhcRn, FreeVars) -rnLHsDerivingClause deriv_strats_ok doc +rnLHsDerivingClause doc (L loc (HsDerivingClause { deriv_clause_ext = noExt , deriv_clause_strategy = dcs , deriv_clause_tys = L loc' dct })) - = do { failIfTc (isJust dcs && not deriv_strats_ok) $ - illegalDerivStrategyErr $ fmap unLoc dcs - ; (dct', fvs) <- mapFvRn (rnHsSigType doc) dct - ; return ( L loc (HsDerivingClause { deriv_clause_ext = noExt - , deriv_clause_strategy = dcs - , deriv_clause_tys = L loc' dct' }) - , fvs ) } -rnLHsDerivingClause _ _ (L _ (XHsDerivingClause _)) + = do { (dcs', dct', fvs) + <- rnLDerivStrategy doc dcs $ \strat_tvs ppr_via_ty -> + mapFvRn (rn_deriv_ty strat_tvs ppr_via_ty) dct + ; pure ( L loc (HsDerivingClause { deriv_clause_ext = noExt + , deriv_clause_strategy = dcs' + , deriv_clause_tys = L loc' dct' }) + , fvs ) } + where + rn_deriv_ty :: [Name] -> SDoc -> LHsSigType GhcPs + -> RnM (LHsSigType GhcRn, FreeVars) + rn_deriv_ty strat_tvs ppr_via_ty deriv_ty@(HsIB {hsib_body = L loc _}) = + rnAndReportFloatingViaTvs strat_tvs loc ppr_via_ty "class" $ + rnHsSigType doc deriv_ty + rn_deriv_ty _ _ (XHsImplicitBndrs _) = panic "rn_deriv_ty" +rnLHsDerivingClause _ (L _ (XHsDerivingClause _)) = panic "rnLHsDerivingClause" +rnLDerivStrategy :: forall a. + HsDocContext + -> Maybe (LDerivStrategy GhcPs) + -> ([Name] -- The tyvars bound by the via type + -> SDoc -- The pretty-printed via type (used for + -- error message reporting) + -> RnM (a, FreeVars)) + -> RnM (Maybe (LDerivStrategy GhcRn), a, FreeVars) +rnLDerivStrategy doc mds thing_inside + = case mds of + Nothing -> boring_case Nothing + Just ds -> do (ds', thing, fvs) <- rn_deriv_strat ds + pure (Just ds', thing, fvs) + where + rn_deriv_strat :: LDerivStrategy GhcPs + -> RnM (LDerivStrategy GhcRn, a, FreeVars) + rn_deriv_strat (L loc ds) = do + let extNeeded :: LangExt.Extension + extNeeded + | ViaStrategy{} <- ds + = LangExt.DerivingVia + | otherwise + = LangExt.DerivingStrategies + + unlessXOptM extNeeded $ + failWith $ illegalDerivStrategyErr ds + + case ds of + StockStrategy -> boring_case (L loc StockStrategy) + AnyclassStrategy -> boring_case (L loc AnyclassStrategy) + NewtypeStrategy -> boring_case (L loc NewtypeStrategy) + ViaStrategy via_ty -> + do (via_ty', fvs1) <- rnHsSigType doc via_ty + let HsIB { hsib_ext = HsIBRn { hsib_vars = via_imp_tvs } + , hsib_body = via_body } = via_ty' + (via_exp_tv_bndrs, _, _) = splitLHsSigmaTy via_body + via_exp_tvs = map hsLTyVarName via_exp_tv_bndrs + via_tvs = via_imp_tvs ++ via_exp_tvs + (thing, fvs2) <- extendTyVarEnvFVRn via_tvs $ + thing_inside via_tvs (ppr via_ty') + pure (L loc (ViaStrategy via_ty'), thing, fvs1 `plusFV` fvs2) + + boring_case :: mds + -> RnM (mds, a, FreeVars) + boring_case mds = do + (thing, fvs) <- thing_inside [] empty + pure (mds, thing, fvs) + +-- | Errors if a @via@ type binds any floating type variables. +-- See @Note [Floating `via` type variables]@ +rnAndReportFloatingViaTvs + :: forall a. Outputable a + => [Name] -- ^ The bound type variables from a @via@ type. + -> SrcSpan -- ^ The source span (for error reporting only). + -> SDoc -- ^ The pretty-printed @via@ type (for error reporting only). + -> String -- ^ A description of what the @via@ type scopes over + -- (for error reporting only). + -> RnM (a, FreeVars) -- ^ The thing the @via@ type scopes over. + -> RnM (a, FreeVars) +rnAndReportFloatingViaTvs tv_names loc ppr_via_ty via_scope_desc thing_inside + = do (thing, thing_fvs) <- thing_inside + setSrcSpan loc $ mapM_ (report_floating_via_tv thing thing_fvs) tv_names + pure (thing, thing_fvs) + where + report_floating_via_tv :: a -> FreeVars -> Name -> RnM () + report_floating_via_tv thing used_names tv_name + = unless (tv_name `elemNameSet` used_names) $ addErr $ vcat + [ text "Type variable" <+> quotes (ppr tv_name) <+> + text "is bound in the" <+> quotes (text "via") <+> + text "type" <+> quotes ppr_via_ty + , text "but is not mentioned in the derived" <+> + text via_scope_desc <+> quotes (ppr thing) <> + text ", which is illegal" ] + +{- +Note [Floating `via` type variables] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Imagine the following `deriving via` clause: + + data Quux + deriving Eq via (Const a Quux) + +This should be rejected. Why? Because it would generate the following instance: + + instance Eq Quux where + (==) = coerce @(Quux -> Quux -> Bool) + @(Const a Quux -> Const a Quux -> Bool) + (==) + +This instance is ill-formed, as the `a` in `Const a Quux` is unbound. The +problem is that `a` is never used anywhere in the derived class `Eq`. Since +`a` is bound but has no use sites, we refer to it as "floating". + +We use the rnAndReportFloatingViaTvs function to check that any type renamed +within the context of the `via` deriving strategy actually uses all bound +`via` type variables, and if it doesn't, it throws an error. +-} + badGadtStupidTheta :: HsDocContext -> SDoc badGadtStupidTheta _ = vcat [text "No context is allowed on a GADT-style data declaration", text "(You can put a context on each constructor, though.)"] -illegalDerivStrategyErr :: Maybe DerivStrategy -> SDoc +illegalDerivStrategyErr :: DerivStrategy GhcPs -> SDoc illegalDerivStrategyErr ds - = vcat [ text "Illegal deriving strategy" <> colon <+> maybe empty ppr ds - , text "Use DerivingStrategies to enable this extension" ] + = vcat [ text "Illegal deriving strategy" <> colon <+> derivStrategyName ds + , text enableStrategy ] + + where + enableStrategy :: String + enableStrategy + | ViaStrategy{} <- ds + = "Use DerivingVia to enable this extension" + | otherwise + = "Use DerivingStrategies to enable this extension" multipleDerivClausesErr :: SDoc multipleDerivClausesErr |