module Language.PureScript.Docs.Convert.ReExports ( updateReExports ) where import Prelude.Compat import Control.Arrow ((&&&), first, second) import Control.Monad import Control.Monad.Reader.Class (MonadReader, ask) import Control.Monad.State.Class (MonadState, gets, modify) import Control.Monad.Trans.Reader (runReaderT) import Control.Monad.Trans.State.Strict (execState) import Data.Either import Data.Map (Map) import Data.Maybe (mapMaybe) import Data.Monoid ((<>)) import qualified Data.Map as Map import Data.Text (Text) import qualified Data.Text as T import Language.PureScript.Docs.Types import qualified Language.PureScript as P -- | -- Given: -- -- * The Imports/Exports Env -- * An order to traverse the modules (which must be topological) -- * A map of modules, indexed by their names, which are assumed to not -- have their re-exports listed yet -- -- This function adds all the missing re-exports. -- updateReExports :: P.Env -> [P.ModuleName] -> (P.ModuleName -> InPackage P.ModuleName) -> Map P.ModuleName Module -> Map P.ModuleName Module updateReExports env order withPackage = execState action where action = void (traverse go order) go mn = do mdl <- lookup' mn reExports <- getReExports env mn let mdl' = mdl { modReExports = map (first withPackage) reExports } modify (Map.insert mn mdl') lookup' mn = do v <- gets (Map.lookup mn) case v of Just v' -> pure v' Nothing -> internalError ("Module missing: " ++ T.unpack (P.runModuleName mn)) -- | -- Collect all of the re-exported declarations for a single module. -- -- We require that modules have already been sorted (P.sortModules) in order to -- ensure that by the time we convert a particular module, all its dependencies -- have already been converted. -- getReExports :: (MonadState (Map P.ModuleName Module) m) => P.Env -> P.ModuleName -> m [(P.ModuleName, [Declaration])] getReExports env mn = case Map.lookup mn env of Nothing -> internalError ("Module missing: " ++ T.unpack (P.runModuleName mn)) Just (_, imports, exports) -> do allExports <- runReaderT (collectDeclarations imports exports) mn pure (filter notLocal allExports) where notLocal = (/= mn) . fst -- | -- Assemble a list of declarations re-exported from a particular module, based -- on the Imports and Exports value for that module, and by extracting the -- declarations from the current state. -- -- This function works by searching through the lists of exported declarations -- in the Exports, and looking them up in the associated Imports value to find -- the module they were imported from. -- -- Additionally: -- -- * Attempts to move re-exported type class members under their parent -- type classes, if possible, or otherwise, "promote" them from -- ChildDeclarations to proper Declarations. -- * Filters data declarations to ensure that only re-exported data -- constructors are listed. -- * Filters type class declarations to ensure that only re-exported type -- class members are listed. -- collectDeclarations :: forall m. (MonadState (Map P.ModuleName Module) m, MonadReader P.ModuleName m) => P.Imports -> P.Exports -> m [(P.ModuleName, [Declaration])] collectDeclarations imports exports = do valsAndMembers <- collect lookupValueDeclaration impVals expVals valOps <- collect lookupValueOpDeclaration impValOps expValOps typeClasses <- collect lookupTypeClassDeclaration impTCs expTCs types <- collect lookupTypeDeclaration impTypes expTypes typeOps <- collect lookupTypeOpDeclaration impTypeOps expTypeOps kinds <- collect lookupKindDeclaration impKinds expKinds (vals, classes) <- handleTypeClassMembers valsAndMembers typeClasses let filteredTypes = filterDataConstructors expCtors types let filteredClasses = filterTypeClassMembers (Map.keys expVals) classes pure (Map.toList (Map.unionsWith (<>) [filteredTypes, filteredClasses, vals, valOps, typeOps, kinds])) where collect :: (Eq a, Show a) => (P.ModuleName -> a -> m (P.ModuleName, [b])) -> [P.ImportRecord a] -> Map a P.ModuleName -> m (Map P.ModuleName [b]) collect lookup' imps exps = do imps' <- traverse (findImport imps) $ Map.toList exps Map.fromListWith (<>) <$> traverse (uncurry lookup') imps' expVals = P.exportedValues exports impVals = concat (Map.elems (P.importedValues imports)) expValOps = P.exportedValueOps exports impValOps = concat (Map.elems (P.importedValueOps imports)) expTypes = Map.map snd (P.exportedTypes exports) impTypes = concat (Map.elems (P.importedTypes imports)) expTypeOps = P.exportedTypeOps exports impTypeOps = concat (Map.elems (P.importedTypeOps imports)) expCtors = concatMap fst (Map.elems (P.exportedTypes exports)) expTCs = P.exportedTypeClasses exports impTCs = concat (Map.elems (P.importedTypeClasses imports)) expKinds = P.exportedKinds exports impKinds = concat (Map.elems (P.importedKinds imports)) -- | -- Given a list of imported declarations (of a particular kind, ie. type, data, -- class, value, etc), and the name of an exported declaration of the same -- kind, together with the module it was originally defined in, return a tuple -- of: -- -- * the module that exported declaration was imported from (note that -- this can be different from the module it was originally defined in, if -- it is a re-export), -- * that same declaration's name. -- -- This function uses a type variable for names because we want to be able to -- instantiate @name@ as both 'P.Ident' and 'P.ProperName'. -- findImport :: (Show name, Eq name, MonadReader P.ModuleName m) => [P.ImportRecord name] -> (name, P.ModuleName) -> m (P.ModuleName, name) findImport imps (name, orig) = let matches (P.ImportRecord qual mn _) = P.disqualify qual == name && mn == orig matching = filter matches imps getQualified (P.Qualified mname _) = mname in case mapMaybe (getQualified . P.importName) matching of -- A value can occur more than once if it is imported twice (eg, if it is -- exported by A, re-exported from A by B, and C imports it from both A -- and B). In this case, we just take its first appearance. (importedFrom:_) -> pure (importedFrom, name) [] -> internalErrorInModule ("findImport: not found: " ++ show (name, orig)) lookupValueDeclaration :: (MonadState (Map P.ModuleName Module) m, MonadReader P.ModuleName m) => P.ModuleName -> P.Ident -> m (P.ModuleName, [Either (Text, P.Constraint, ChildDeclaration) Declaration]) lookupValueDeclaration importedFrom ident = do decls <- lookupModuleDeclarations "lookupValueDeclaration" importedFrom let rs = filter (\d -> declTitle d == P.showIdent ident && (isValue d || isValueAlias d)) decls errOther other = internalErrorInModule ("lookupValueDeclaration: unexpected result:\n" ++ "other: " ++ show other ++ "\n" ++ "ident: " ++ show ident ++ "\n" ++ "decls: " ++ show decls) case rs of [r] -> pure (importedFrom, [Right r]) [] -> -- It's a type class member. -- Note that we need to filter based on the child declaration info using -- `isTypeClassMember` anyway, because child declarations of type classes -- are not necessarily members; they could also be instances. let allTypeClassChildDecls = decls |> mapMaybe (\d -> (d,) <$> typeClassConstraintFor d) |> concatMap (\(d, constr) -> map (declTitle d, constr,) (declChildren d)) matchesIdent cdecl = cdeclTitle cdecl == P.showIdent ident matchesAndIsTypeClassMember = uncurry (&&) . (matchesIdent &&& isTypeClassMember) in case filter (matchesAndIsTypeClassMember . thd) allTypeClassChildDecls of [r'] -> pure (importedFrom, [Left r']) other -> errOther other other -> errOther other where thd :: (a, b, c) -> c thd (_, _, x) = x lookupValueOpDeclaration :: (MonadState (Map P.ModuleName Module) m, MonadReader P.ModuleName m) => P.ModuleName -> P.OpName 'P.ValueOpName -> m (P.ModuleName, [Declaration]) lookupValueOpDeclaration importedFrom op = do decls <- lookupModuleDeclarations "lookupValueOpDeclaration" importedFrom case filter (\d -> declTitle d == P.showOp op && isValueAlias d) decls of [d] -> pure (importedFrom, [d]) other -> internalErrorInModule ("lookupValueOpDeclaration: unexpected result for: " ++ show other) -- | -- Extract a particular type declaration. For data declarations, constructors -- are only included in the output if they are listed in the arguments. -- lookupTypeDeclaration :: (MonadState (Map P.ModuleName Module) m, MonadReader P.ModuleName m) => P.ModuleName -> P.ProperName 'P.TypeName -> m (P.ModuleName, [Declaration]) lookupTypeDeclaration importedFrom ty = do decls <- lookupModuleDeclarations "lookupTypeDeclaration" importedFrom let ds = filter (\d -> declTitle d == P.runProperName ty && isType d) decls case ds of [d] -> pure (importedFrom, [d]) other -> internalErrorInModule ("lookupTypeDeclaration: unexpected result: " ++ show other) lookupTypeOpDeclaration :: (MonadState (Map P.ModuleName Module) m,MonadReader P.ModuleName m) => P.ModuleName -> P.OpName 'P.TypeOpName -> m (P.ModuleName, [Declaration]) lookupTypeOpDeclaration importedFrom tyOp = do decls <- lookupModuleDeclarations "lookupTypeOpDeclaration" importedFrom let ds = filter (\d -> declTitle d == ("type " <> P.showOp tyOp) && isTypeAlias d) decls case ds of [d] -> pure (importedFrom, [d]) other -> internalErrorInModule ("lookupTypeOpDeclaration: unexpected result: " ++ show other) lookupTypeClassDeclaration :: (MonadState (Map P.ModuleName Module) m, MonadReader P.ModuleName m) => P.ModuleName -> P.ProperName 'P.ClassName -> m (P.ModuleName, [Declaration]) lookupTypeClassDeclaration importedFrom tyClass = do decls <- lookupModuleDeclarations "lookupTypeClassDeclaration" importedFrom let ds = filter (\d -> declTitle d == P.runProperName tyClass && isTypeClass d) decls case ds of [d] -> pure (importedFrom, [d]) other -> internalErrorInModule ("lookupTypeClassDeclaration: unexpected result: " ++ (unlines . map show) other) lookupKindDeclaration :: (MonadState (Map P.ModuleName Module) m, MonadReader P.ModuleName m) => P.ModuleName -> P.ProperName 'P.KindName -> m (P.ModuleName, [Declaration]) lookupKindDeclaration importedFrom kind = do decls <- lookupModuleDeclarations "lookupKindDeclaration" importedFrom let ds = filter (\d -> declTitle d == P.runProperName kind && isKind d) decls case ds of [d] -> pure (importedFrom, [d]) other -> internalErrorInModule ("lookupKindDeclaration: unexpected result: " ++ show other) -- | -- Get the full list of declarations for a particular module out of the -- state, or raise an internal error if it is not there. -- lookupModuleDeclarations :: (MonadState (Map P.ModuleName Module) m, MonadReader P.ModuleName m) => String -> P.ModuleName -> m [Declaration] lookupModuleDeclarations definedIn moduleName = do mmdl <- gets (Map.lookup moduleName) case mmdl of Nothing -> internalErrorInModule (definedIn ++ ": module missing: " ++ T.unpack (P.runModuleName moduleName)) Just mdl -> pure (allDeclarations mdl) handleTypeClassMembers :: (MonadReader P.ModuleName m) => Map P.ModuleName [Either (Text, P.Constraint, ChildDeclaration) Declaration] -> Map P.ModuleName [Declaration] -> m (Map P.ModuleName [Declaration], Map P.ModuleName [Declaration]) handleTypeClassMembers valsAndMembers typeClasses = let moduleEnvs = Map.unionWith (<>) (fmap valsAndMembersToEnv valsAndMembers) (fmap typeClassesToEnv typeClasses) in moduleEnvs |> traverse handleEnv |> fmap splitMap valsAndMembersToEnv :: [Either (Text, P.Constraint, ChildDeclaration) Declaration] -> TypeClassEnv valsAndMembersToEnv xs = let (envUnhandledMembers, envValues) = partitionEithers xs envTypeClasses = [] in TypeClassEnv{..} typeClassesToEnv :: [Declaration] -> TypeClassEnv typeClassesToEnv classes = TypeClassEnv { envUnhandledMembers = [] , envValues = [] , envTypeClasses = classes } -- | -- An intermediate data type, used for either moving type class members under -- their parent type classes, or promoting them to normal Declaration values -- if their parent type class has not been re-exported. -- data TypeClassEnv = TypeClassEnv { -- | -- Type class members which have not yet been dealt with. The Text is the -- name of the type class they belong to, and the constraint is used to -- make sure that they have the correct type if they get promoted. -- envUnhandledMembers :: [(Text, P.Constraint, ChildDeclaration)] -- | -- A list of normal value declarations. Type class members will be added to -- this list if their parent type class is not available. -- , envValues :: [Declaration] -- | -- A list of type class declarations. Type class members will be added to -- their parents in this list, if they exist. -- , envTypeClasses :: [Declaration] } deriving (Show) instance Monoid TypeClassEnv where mempty = TypeClassEnv mempty mempty mempty mappend (TypeClassEnv a1 b1 c1) (TypeClassEnv a2 b2 c2) = TypeClassEnv (a1 <> a2) (b1 <> b2) (c1 <> c2) -- | -- Take a TypeClassEnv and handle all of the type class members in it, either -- adding them to their parent classes, or promoting them to normal Declaration -- values. -- -- Returns a tuple of (values, type classes). -- handleEnv :: (MonadReader P.ModuleName m) => TypeClassEnv -> m ([Declaration], [Declaration]) handleEnv TypeClassEnv{..} = envUnhandledMembers |> foldM go (envValues, mkMap envTypeClasses) |> fmap (second Map.elems) where mkMap = Map.fromList . map (declTitle &&& id) go (values, tcs) (title, constraint, childDecl) = case Map.lookup title tcs of Just _ -> -- Leave the state unchanged; if the type class is there, the child -- will be too. pure (values, tcs) Nothing -> do c <- promoteChild constraint childDecl pure (c : values, tcs) promoteChild constraint ChildDeclaration{..} = case cdeclInfo of ChildTypeClassMember typ -> pure Declaration { declTitle = cdeclTitle , declComments = cdeclComments , declSourceSpan = cdeclSourceSpan , declChildren = [] , declInfo = ValueDeclaration (addConstraint constraint typ) } _ -> internalErrorInModule ("handleEnv: Bad child declaration passed to promoteChild: " ++ T.unpack cdeclTitle) addConstraint constraint = P.quantify . P.moveQuantifiersToFront . P.ConstrainedType constraint splitMap :: Map k (v1, v2) -> (Map k v1, Map k v2) splitMap = fmap fst &&& fmap snd -- | -- Given a list of exported constructor names, remove any data constructor -- names in the provided Map of declarations which are not in the list. -- filterDataConstructors :: [P.ProperName 'P.ConstructorName] -> Map P.ModuleName [Declaration] -> Map P.ModuleName [Declaration] filterDataConstructors = filterExportedChildren isDataConstructor P.runProperName -- | -- Given a list of exported type class member names, remove any data -- type class member names in the provided Map of declarations which are not in -- the list. -- filterTypeClassMembers :: [P.Ident] -> Map P.ModuleName [Declaration] -> Map P.ModuleName [Declaration] filterTypeClassMembers = filterExportedChildren isTypeClassMember P.showIdent filterExportedChildren :: (Functor f) => (ChildDeclaration -> Bool) -> (name -> Text) -> [name] -> f [Declaration] -> f [Declaration] filterExportedChildren isTargetedKind runName expNames = fmap filterDecls where filterDecls = map $ filterChildren $ \c -> not (isTargetedKind c) || cdeclTitle c `elem` expNames' expNames' = map runName expNames allDeclarations :: Module -> [Declaration] allDeclarations Module{..} = modDeclarations ++ concatMap snd modReExports (|>) :: a -> (a -> b) -> b x |> f = f x internalError :: String -> a internalError = P.internalError . ("Docs.Convert.ReExports: " ++) internalErrorInModule :: (MonadReader P.ModuleName m) => String -> m a internalErrorInModule msg = do mn <- ask internalError ("while collecting re-exports for module: " ++ T.unpack (P.runModuleName mn) ++ ", " ++ msg) -- | -- If the provided Declaration is a TypeClassDeclaration, construct an -- appropriate Constraint for use with the types of its members. -- typeClassConstraintFor :: Declaration -> Maybe P.Constraint typeClassConstraintFor Declaration{..} = case declInfo of TypeClassDeclaration tyArgs _ _ -> Just (P.Constraint (P.Qualified Nothing (P.ProperName declTitle)) (mkConstraint tyArgs) Nothing) _ -> Nothing where mkConstraint = map (P.TypeVar . fst)