purescript-0.15.3: src/Language/PureScript/Sugar/BindingGroups.hs
-- |
-- This module implements the desugaring pass which creates binding groups from sets of
-- mutually-recursive value declarations and mutually-recursive type declarations.
--
module Language.PureScript.Sugar.BindingGroups
( createBindingGroups
, createBindingGroupsModule
, collapseBindingGroups
) where
import Prelude.Compat
import Protolude (ordNub)
import Control.Monad ((<=<), guard)
import Control.Monad.Error.Class (MonadError(..))
import Data.Graph
import Data.List (intersect, (\\))
import Data.List.NonEmpty (NonEmpty((:|)), nonEmpty)
import Data.Foldable (find)
import Data.Functor (($>))
import Data.Maybe (isJust, mapMaybe)
import qualified Data.List.NonEmpty as NEL
import qualified Data.Set as S
import Language.PureScript.AST
import Language.PureScript.Crash
import Language.PureScript.Environment
import Language.PureScript.Errors hiding (nonEmpty)
import Language.PureScript.Names
import Language.PureScript.Types
data VertexType
= VertexDefinition
| VertexKindSignature
| VertexRoleDeclaration
deriving (Eq, Ord, Show)
-- |
-- Replace all sets of mutually-recursive declarations in a module with binding groups
--
createBindingGroupsModule
:: (MonadError MultipleErrors m)
=> Module
-> m Module
createBindingGroupsModule (Module ss coms name ds exps) =
Module ss coms name <$> createBindingGroups name ds <*> pure exps
createBindingGroups
:: forall m
. (MonadError MultipleErrors m)
=> ModuleName
-> [Declaration]
-> m [Declaration]
createBindingGroups moduleName = mapM f <=< handleDecls
where
(f, _, _) = everywhereOnValuesTopDownM return handleExprs return
handleExprs :: Expr -> m Expr
handleExprs (Let w ds val) = (\ds' -> Let w ds' val) <$> handleDecls ds
handleExprs other = return other
-- |
-- Replace all sets of mutually-recursive declarations with binding groups
--
handleDecls :: [Declaration] -> m [Declaration]
handleDecls ds = do
let values = mapMaybe (fmap (fmap extractGuardedExpr) . getValueDeclaration) ds
kindDecls = (,VertexKindSignature) <$> filter isKindDecl ds
dataDecls = (,VertexDefinition) <$> filter (\a -> isDataDecl a || isExternDataDecl a || isTypeSynonymDecl a || isTypeClassDecl a) ds
roleDecls = (,VertexRoleDeclaration) <$> filter isRoleDecl ds
roleAnns = declTypeName . fst <$> roleDecls
kindSigs = declTypeName . fst <$> kindDecls
typeSyns = declTypeName <$> filter isTypeSynonymDecl ds
nonTypeSynKindSigs = kindSigs \\ typeSyns
allDecls = kindDecls ++ dataDecls ++ roleDecls
allProperNames = declTypeName . fst <$> allDecls
mkVert (d, vty) =
let names = usedTypeNames moduleName d `intersect` allProperNames
name = declTypeName d
-- If a dependency of a kind signature has a kind signature, than that's all we need to
-- depend on, except in the case that we are using a type synonym. In order to expand
-- the type synonym, we must depend on the synonym declaration itself.
--
-- Arguably, type declarations (as opposed to just kind signatures) could also depend
-- on kind signatures when present. Attempting this caused one known issue (#4038); the
-- type checker might not expect type declarations not to be preceded or grouped by
-- their actual dependencies in all cases. But in principle, if done carefully, this
-- approach could be used to reduce the number or size of data binding group cycles.
-- (It's critical that kind signatures not appear in groups, which is why they get
-- special treatment.)
vtype n
| vty == VertexKindSignature && n `elem` nonTypeSynKindSigs = VertexKindSignature
| otherwise = VertexDefinition
deps = fmap (\n -> (n, vtype n)) names
self
| vty == VertexDefinition =
(guard (name `elem` kindSigs) $> (name, VertexKindSignature))
++ (guard (name `elem` roleAnns && not (isExternDataDecl d)) $> (name, VertexRoleDeclaration))
| vty == VertexRoleDeclaration = [(name, VertexDefinition)]
| otherwise = []
in (d, (name, vty), self ++ deps)
dataVerts = fmap mkVert allDecls
dataBindingGroupDecls <- parU (stronglyConnCompR dataVerts) toDataBindingGroup
let allIdents = fmap valdeclIdent values
valueVerts = fmap (\d -> (d, valdeclIdent d, usedIdents moduleName d `intersect` allIdents)) values
bindingGroupDecls <- parU (stronglyConnComp valueVerts) (toBindingGroup moduleName)
return $ filter isImportDecl ds ++
dataBindingGroupDecls ++
filter isTypeClassInstanceDecl ds ++
filter isFixityDecl ds ++
filter isExternDecl ds ++
bindingGroupDecls
where
extractGuardedExpr [MkUnguarded expr] = expr
extractGuardedExpr _ = internalError "Expected Guards to have been desugared in handleDecls."
-- |
-- Collapse all binding groups to individual declarations
--
collapseBindingGroups :: [Declaration] -> [Declaration]
collapseBindingGroups =
let (f, _, _) = everywhereOnValues id flattenBindingGroupsForValue id
in fmap f . flattenBindingGroups
flattenBindingGroupsForValue :: Expr -> Expr
flattenBindingGroupsForValue (Let w ds val) = Let w (flattenBindingGroups ds) val
flattenBindingGroupsForValue other = other
flattenBindingGroups :: [Declaration] -> [Declaration]
flattenBindingGroups = concatMap go
where
go (DataBindingGroupDeclaration ds) = NEL.toList ds
go (BindingGroupDeclaration ds) =
NEL.toList $ fmap (\((sa, ident), nameKind, val) ->
ValueDecl sa ident nameKind [] [MkUnguarded val]) ds
go other = [other]
usedIdents :: ModuleName -> ValueDeclarationData Expr -> [Ident]
usedIdents moduleName = ordNub . usedIdents' S.empty . valdeclExpression
where
def _ _ = []
(_, usedIdents', _, _, _) = everythingWithScope def usedNamesE def def def
usedNamesE :: S.Set ScopedIdent -> Expr -> [Ident]
usedNamesE scope (Var _ (Qualified (BySourcePos _) name))
| LocalIdent name `S.notMember` scope = [name]
usedNamesE scope (Var _ (Qualified (ByModuleName moduleName') name))
| moduleName == moduleName' && ToplevelIdent name `S.notMember` scope = [name]
usedNamesE _ _ = []
usedImmediateIdents :: ModuleName -> Declaration -> [Ident]
usedImmediateIdents moduleName =
let (f, _, _, _, _) = everythingWithContextOnValues True [] (++) def usedNamesE def def def
in ordNub . f
where
def s _ = (s, [])
usedNamesE :: Bool -> Expr -> (Bool, [Ident])
usedNamesE True (Var _ (Qualified (BySourcePos _) name)) = (True, [name])
usedNamesE True (Var _ (Qualified (ByModuleName moduleName') name))
| moduleName == moduleName' = (True, [name])
usedNamesE True (Abs _ _) = (False, [])
usedNamesE scope _ = (scope, [])
usedTypeNames :: ModuleName -> Declaration -> [ProperName 'TypeName]
usedTypeNames moduleName = go
where
(f, _, _, _, _) = accumTypes (everythingOnTypes (++) usedNames)
go :: Declaration -> [ProperName 'TypeName]
go decl = ordNub (f decl <> usedNamesForTypeClassDeps decl)
usedNames :: SourceType -> [ProperName 'TypeName]
usedNames (ConstrainedType _ con _) = usedConstraint con
usedNames (TypeConstructor _ (Qualified (ByModuleName moduleName') name))
| moduleName == moduleName' = [name]
usedNames _ = []
usedConstraint :: SourceConstraint -> [ProperName 'TypeName]
usedConstraint (Constraint _ (Qualified (ByModuleName moduleName') name) _ _ _)
| moduleName == moduleName' = [coerceProperName name]
usedConstraint _ = []
usedNamesForTypeClassDeps :: Declaration -> [ProperName 'TypeName]
usedNamesForTypeClassDeps (TypeClassDeclaration _ _ _ deps _ _) = foldMap usedConstraint deps
usedNamesForTypeClassDeps _ = []
declTypeName :: Declaration -> ProperName 'TypeName
declTypeName (DataDeclaration _ _ pn _ _) = pn
declTypeName (ExternDataDeclaration _ pn _) = pn
declTypeName (TypeSynonymDeclaration _ pn _ _) = pn
declTypeName (TypeClassDeclaration _ pn _ _ _ _) = coerceProperName pn
declTypeName (KindDeclaration _ _ pn _) = pn
declTypeName (RoleDeclaration (RoleDeclarationData _ pn _)) = pn
declTypeName _ = internalError "Expected DataDeclaration"
-- |
-- Convert a group of mutually-recursive dependencies into a BindingGroupDeclaration (or simple ValueDeclaration).
--
--
toBindingGroup
:: forall m
. (MonadError MultipleErrors m)
=> ModuleName
-> SCC (ValueDeclarationData Expr)
-> m Declaration
toBindingGroup _ (AcyclicSCC d) = return (mkDeclaration d)
toBindingGroup moduleName (CyclicSCC ds') = do
-- Once we have a mutually-recursive group of declarations, we need to sort
-- them further by their immediate dependencies (those outside function
-- bodies). In particular, this is relevant for type instance dictionaries
-- whose members require other type instances (for example, functorEff
-- defines (<$>) = liftA1, which depends on applicativeEff). Note that
-- superclass references are still inside functions, so don't count here.
-- If we discover declarations that still contain mutually-recursive
-- immediate references, we're guaranteed to get an undefined reference at
-- runtime, so treat this as an error. See also github issue #365.
BindingGroupDeclaration . NEL.fromList <$> mapM toBinding (stronglyConnComp valueVerts)
where
idents :: [Ident]
idents = fmap (\(_, i, _) -> i) valueVerts
valueVerts :: [(ValueDeclarationData Expr, Ident, [Ident])]
valueVerts = fmap (\d -> (d, valdeclIdent d, usedImmediateIdents moduleName (mkDeclaration d) `intersect` idents)) ds'
toBinding :: SCC (ValueDeclarationData Expr) -> m ((SourceAnn, Ident), NameKind, Expr)
toBinding (AcyclicSCC d) = return $ fromValueDecl d
toBinding (CyclicSCC ds) = throwError $ foldMap cycleError ds
cycleError :: ValueDeclarationData Expr -> MultipleErrors
cycleError (ValueDeclarationData (ss, _) n _ _ _) = errorMessage' ss $ CycleInDeclaration n
toDataBindingGroup
:: MonadError MultipleErrors m
=> Ord a
=> SCC (Declaration, (ProperName 'TypeName, a), [(ProperName 'TypeName, a)])
-> m Declaration
toDataBindingGroup (AcyclicSCC (d, _, _)) = return d
toDataBindingGroup (CyclicSCC ds')
| Just kds@((ss, _):|_) <- nonEmpty $ concatMap (kindDecl . getDecl) ds' = throwError . errorMessage' ss . CycleInKindDeclaration $ fmap snd kds
| not (null typeSynonymCycles) =
throwError
. MultipleErrors
. fmap (\syns -> ErrorMessage [positionedError . declSourceSpan . getDecl $ NEL.head syns] . CycleInTypeSynonym $ fmap (fst . getName) syns)
$ typeSynonymCycles
| otherwise = return . DataBindingGroupDeclaration . NEL.fromList $ getDecl <$> ds'
where
kindDecl (KindDeclaration sa _ pn _) = [(fst sa, Qualified ByNullSourcePos pn)]
kindDecl (ExternDataDeclaration sa pn _) = [(fst sa, Qualified ByNullSourcePos pn)]
kindDecl _ = []
getDecl (decl, _, _) = decl
getName (_, name, _) = name
lookupVert name = find ((==) name . getName) ds'
onlySynonyms (decl, name, deps) = do
guard . isJust $ isTypeSynonym decl
pure (decl, name, filter (maybe False (isJust . isTypeSynonym . getDecl) . lookupVert) deps)
isCycle (CyclicSCC c) = nonEmpty c
isCycle _ = Nothing
typeSynonymCycles =
mapMaybe isCycle . stronglyConnCompR . mapMaybe onlySynonyms $ ds'
isTypeSynonym :: Declaration -> Maybe (ProperName 'TypeName)
isTypeSynonym (TypeSynonymDeclaration _ pn _ _) = Just pn
isTypeSynonym _ = Nothing
mkDeclaration :: ValueDeclarationData Expr -> Declaration
mkDeclaration = ValueDeclaration . fmap (pure . MkUnguarded)
fromValueDecl :: ValueDeclarationData Expr -> ((SourceAnn, Ident), NameKind, Expr)
fromValueDecl (ValueDeclarationData sa ident nameKind [] val) = ((sa, ident), nameKind, val)
fromValueDecl ValueDeclarationData{} = internalError "Binders should have been desugared"