summaryrefslogtreecommitdiff
path: root/compiler/rename/RnSource.hs
diff options
context:
space:
mode:
authorRyan Scott <ryan.gl.scott@gmail.com>2018-06-04 21:20:02 -0400
committerBen Gamari <ben@smart-cactus.org>2018-06-04 22:37:19 -0400
commit8ed8b037fee9611b1c4ef49adb6cf50bbd929a27 (patch)
treeff3907f0412085a78e694597c1bdba700740403f /compiler/rename/RnSource.hs
parent85309a3cda367425cca727dfa45e5e6c63b47391 (diff)
downloadhaskell-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.hs158
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