{-# LANGUAGE CPP #-}
module Checks.ExportCheck (exportCheck, expandExports) where
#if __GLASGOW_HASKELL__ >= 804
import Prelude hiding ((<>))
#endif
#if __GLASGOW_HASKELL__ < 710
import Control.Applicative ((<$>))
#endif
import Control.Monad (unless)
import qualified Control.Monad.State as S (State, runState, gets, modify)
import Data.List (nub, union)
import qualified Data.Map as Map ( Map, elems, empty, insert
, insertWith, lookup, toList )
import Data.Maybe (fromMaybe)
import qualified Data.Set as Set ( Set, empty, fromList, insert
, member, toList )
import Curry.Base.Ident
import Curry.Base.Position
import Curry.Base.SpanInfo
import Curry.Base.Pretty
import Curry.Syntax
import Base.Messages (Message, internalError, posMessage)
import Base.TopEnv (allEntities, origName, localBindings, moduleImports)
import Base.Types ( Type (..), unapplyType, arrowBase, PredType (..)
, DataConstr (..), constrIdent, recLabels
, ClassMethod, methodName
, TypeScheme (..), ExistTypeScheme (..) )
import Base.Utils (findMultiples)
import Env.ModuleAlias (AliasEnv)
import Env.TypeConstructor (TCEnv, TypeInfo (..), qualLookupTypeInfoUnique)
import Env.Value (ValueEnv, ValueInfo (..), qualLookupValueUnique)
currentModuleName :: String
currentModuleName = "Checks.ExportCheck"
expandExports :: ModuleIdent -> AliasEnv -> TCEnv -> ValueEnv
-> Maybe ExportSpec -> ExportSpec
expandExports m aEnv tcEnv tyEnv spec = Exporting (exportSpan spec) es
where
exportSpan (Just (Exporting spi _)) = spi
exportSpan Nothing = NoSpanInfo
es = expand m aEnv tcEnv tyEnv spec
exportCheck :: ModuleIdent -> AliasEnv -> TCEnv -> ValueEnv
-> Maybe ExportSpec -> [Message]
exportCheck m aEnv tcEnv tyEnv spec = case check m aEnv tcEnv tyEnv spec of
[] -> checkNonUniqueness $ expand m aEnv tcEnv tyEnv spec
ms -> ms
data ECState = ECState
{ moduleIdent :: ModuleIdent
, importedMods :: Set.Set ModuleIdent
, tyConsEnv :: TCEnv
, valueEnv :: ValueEnv
, errors :: [Message]
}
type ECM a = S.State ECState a
runECM :: ECM a -> ModuleIdent -> AliasEnv -> TCEnv -> ValueEnv -> (a, [Message])
runECM ecm m aEnv tcEnv tyEnv
= let (a, s') = S.runState ecm initState in (a, reverse $ errors s')
where
initState = ECState m imported tcEnv tyEnv []
imported = Set.fromList (Map.elems aEnv)
getModuleIdent :: ECM ModuleIdent
getModuleIdent = S.gets moduleIdent
getImportedModules :: ECM (Set.Set ModuleIdent)
getImportedModules = S.gets importedMods
getTyConsEnv :: ECM TCEnv
getTyConsEnv = S.gets tyConsEnv
getValueEnv :: ECM ValueEnv
getValueEnv = S.gets valueEnv
report :: Message -> ECM ()
report err = S.modify (\ s -> s { errors = err : errors s })
ok :: ECM ()
ok = return ()
check :: ModuleIdent -> AliasEnv -> TCEnv -> ValueEnv -> Maybe ExportSpec
-> [Message]
check m aEnv tcEnv tyEnv spec = snd $ runECM (checkSpec spec) m aEnv tcEnv tyEnv
checkSpec :: Maybe ExportSpec -> ECM ()
checkSpec (Just (Exporting _ es)) = mapM_ checkExport es
checkSpec Nothing = ok
checkExport :: Export -> ECM ()
checkExport (Export _ x ) = checkThing x
checkExport (ExportTypeWith _ tc cs) = checkTypeWith tc cs
checkExport (ExportTypeAll _ tc ) = checkTypeAll tc
checkExport (ExportModule _ em ) = checkModule em
checkThing :: QualIdent -> ECM ()
checkThing tc = do
m <- getModuleIdent
tcEnv <- getTyConsEnv
case qualLookupTypeInfoUnique m tc tcEnv of
[] -> checkThing' tc Nothing
[t] -> checkThing' tc (Just [ExportTypeWith NoSpanInfo (origName t) []])
ts -> report (errAmbiguousType tc ts)
checkThing' :: QualIdent -> Maybe [Export] -> ECM ()
checkThing' f tcExport = do
m <- getModuleIdent
tyEnv <- getValueEnv
case qualLookupValueUnique m f tyEnv of
[] -> justTcOr errUndefinedName
[v] -> case v of
Value _ _ _ _ -> ok
Label _ _ _ -> report $ errOutsideTypeLabel f (getTc v)
_ -> justTcOr $ flip errOutsideTypeConstructor (getTc v)
fs -> report (errAmbiguousName f fs)
where
justTcOr errFun = maybe (report $ errFun f) (const ok) tcExport
getTc (DataConstructor _ _ _ (ForAllExist _ _ (PredType _ ty))) = getTc' ty
getTc (NewtypeConstructor _ _ (ForAllExist _ _ (PredType _ ty))) = getTc' ty
getTc (Label _ _ (ForAll _ (PredType _ (TypeArrow tc' _)))) =
let (TypeConstructor tc, _) = unapplyType False tc' in tc
getTc err = internalError $ currentModuleName ++ ".checkThing'.getTc: " ++ show err
getTc' ty = let (TypeConstructor tc) = arrowBase ty in tc
checkTypeWith :: QualIdent -> [Ident] -> ECM ()
checkTypeWith tc xs = do
m <- getModuleIdent
tcEnv <- getTyConsEnv
case qualLookupTypeInfoUnique m tc tcEnv of
[] -> report (errUndefinedTypeOrClass tc)
[DataType _ _ cs] ->
mapM_ (checkElement errUndefinedElement (visibleElems cs )) xs'
[RenamingType _ _ c] ->
mapM_ (checkElement errUndefinedElement (visibleElems [c])) xs'
[TypeClass _ _ ms] ->
mapM_ (checkElement errUndefinedMethod (visibleMethods ms)) xs'
[_] -> report (errNonDataTypeOrTypeClass tc)
ts -> report (errAmbiguousType tc ts)
where
xs' = nub xs
checkElement err cs' c = unless (c `elem` cs') $ report $ err tc c
checkTypeAll :: QualIdent -> ECM ()
checkTypeAll tc = do
m <- getModuleIdent
tcEnv <- getTyConsEnv
case qualLookupTypeInfoUnique m tc tcEnv of
[] -> report (errUndefinedTypeOrClass tc)
[DataType _ _ _] -> ok
[RenamingType _ _ _] -> ok
[TypeClass _ _ _] -> ok
[_] -> report (errNonDataTypeOrTypeClass tc)
ts -> report (errAmbiguousType tc ts)
checkModule :: ModuleIdent -> ECM ()
checkModule em = do
isLocal <- (em ==) <$> getModuleIdent
isForeign <- (Set.member em) <$> getImportedModules
unless (isLocal || isForeign) $ report $ errModuleNotImported em
checkNonUniqueness :: [Export] -> [Message]
checkNonUniqueness es = map errMultipleType (findMultiples types )
++ map errMultipleName (findMultiples values)
where
types = [ unqualify tc | ExportTypeWith _ tc _ <- es ]
values = [ c | ExportTypeWith _ _ cs <- es, c <- cs ]
++ [ unqualify f | Export _ f <- es ]
expand :: ModuleIdent -> AliasEnv -> TCEnv -> ValueEnv -> Maybe ExportSpec
-> [Export]
expand m aEnv tcEnv tyEnv spec
= fst $ runECM ((joinExports . canonExports tcEnv) <$> expandSpec spec)
m aEnv tcEnv tyEnv
expandSpec :: Maybe ExportSpec -> ECM [Export]
expandSpec (Just (Exporting _ es)) = concat <$> mapM expandExport es
expandSpec Nothing = expandLocalModule
expandExport :: Export -> ECM [Export]
expandExport (Export _ x) = expandThing x
expandExport (ExportTypeWith _ tc cs) = expandTypeWith tc cs
expandExport (ExportTypeAll _ tc) = expandTypeAll tc
expandExport (ExportModule _ em) = expandModule em
expandThing :: QualIdent -> ECM [Export]
expandThing tc = do
m <- getModuleIdent
tcEnv <- getTyConsEnv
case qualLookupTypeInfoUnique m tc tcEnv of
[] -> expandThing' tc Nothing
[t] -> expandThing' tc
(Just [ExportTypeWith NoSpanInfo (origName t @> tc) []])
err -> internalError $ currentModuleName ++ ".expandThing: " ++ show err
expandThing' :: QualIdent -> Maybe [Export] -> ECM [Export]
expandThing' f tcExport = do
m <- getModuleIdent
tyEnv <- getValueEnv
case qualLookupValueUnique m f tyEnv of
[Value f' _ _ _]
-> return $ Export NoSpanInfo (f' @> f) : fromMaybe [] tcExport
_
-> return $ fromMaybe [] tcExport
expandTypeWith :: QualIdent -> [Ident] -> ECM [Export]
expandTypeWith tc xs = do
m <- getModuleIdent
tcEnv <- getTyConsEnv
case qualLookupTypeInfoUnique m tc tcEnv of
[t] -> return [ExportTypeWith NoSpanInfo (origName t @> tc) $ nub xs]
err -> internalError $ currentModuleName ++ ".expandTypeWith: " ++ show err
expandTypeAll :: QualIdent -> ECM [Export]
expandTypeAll tc = do
m <- getModuleIdent
tcEnv <- getTyConsEnv
case qualLookupTypeInfoUnique m tc tcEnv of
[t] -> return [exportType t]
err -> internalError $ currentModuleName ++ ".expandTypeAll: " ++ show err
expandModule :: ModuleIdent -> ECM [Export]
expandModule em = do
isLocal <- (em ==) <$> getModuleIdent
isForeign <- (Set.member em) <$> getImportedModules
locals <- if isLocal then expandLocalModule else return []
foreigns <- if isForeign then expandImportedModule em else return []
return $ locals ++ foreigns
expandLocalModule :: ECM [Export]
expandLocalModule = do
tcEnv <- getTyConsEnv
tyEnv <- getValueEnv
return $
[ exportType t | (_, t) <- localBindings tcEnv ]
++ [ Export NoSpanInfo f'
| (f, Value f' _ _ _) <- localBindings tyEnv, hasGlobalScope f ]
++ [ Export NoSpanInfo l'
| (l, Label l' _ _) <- localBindings tyEnv, hasGlobalScope l ]
expandImportedModule :: ModuleIdent -> ECM [Export]
expandImportedModule m = do
tcEnv <- getTyConsEnv
tyEnv <- getValueEnv
return $ [exportType t | (_, t) <- moduleImports m tcEnv]
++ [Export NoSpanInfo f | (_, Value f _ _ _) <- moduleImports m tyEnv]
++ [Export NoSpanInfo l | (_, Label l _ _) <- moduleImports m tyEnv]
exportType :: TypeInfo -> Export
exportType t = ExportTypeWith NoSpanInfo tc xs
where tc = origName t
xs = elements t
canonExports :: TCEnv -> [Export] -> [Export]
canonExports tcEnv es = map (canonExport (canonLabels tcEnv es)) es
canonExport :: Map.Map QualIdent Export -> Export -> Export
canonExport ls (Export spi x) =
fromMaybe (Export spi x) (Map.lookup x ls)
canonExport _ (ExportTypeWith spi tc xs) = ExportTypeWith spi tc xs
canonExport _ e = internalError $
currentModuleName ++ ".canonExport: " ++ show e
canonLabels :: TCEnv -> [Export] -> Map.Map QualIdent Export
canonLabels tcEnv es = foldr bindLabels Map.empty (allEntities tcEnv)
where
tcs = [tc | ExportTypeWith _ tc _ <- es]
bindLabels t ls
| tc' `elem` tcs = foldr (bindLabel tc') ls (elements t)
| otherwise = ls
where
tc' = origName t
bindLabel tc x =
Map.insert (qualifyLike tc x) (ExportTypeWith NoSpanInfo tc [x])
joinExports :: [Export] -> [Export]
joinExports es = [ExportTypeWith NoSpanInfo tc cs | (tc, cs) <- joinedTypes]
++ [Export NoSpanInfo f | f <- joinedFuncs]
where joinedTypes = Map.toList $ foldr joinType Map.empty es
joinedFuncs = Set.toList $ foldr joinFun Set.empty es
joinType :: Export -> Map.Map QualIdent [Ident] -> Map.Map QualIdent [Ident]
joinType (Export _ _) tcs = tcs
joinType (ExportTypeWith _ tc cs) tcs = Map.insertWith union tc cs tcs
joinType export _ = internalError $
currentModuleName ++ ".joinType: " ++ show export
joinFun :: Export -> Set.Set QualIdent -> Set.Set QualIdent
joinFun (Export _ f) fs = f `Set.insert` fs
joinFun (ExportTypeWith _ _ _) fs = fs
joinFun export _ = internalError $
currentModuleName ++ ".joinFun: " ++ show export
elements :: TypeInfo -> [Ident]
elements (DataType _ _ cs) = visibleElems cs
elements (RenamingType _ _ c) = visibleElems [c]
elements (AliasType _ _ _ _) = []
elements (TypeClass _ _ ms) = visibleMethods ms
elements (TypeVar _) =
error "Checks.ExportCheck.elements: type variable"
visibleElems :: [DataConstr] -> [Ident]
visibleElems cs = map constrIdent cs ++ (nub (concatMap recLabels cs))
visibleMethods :: [ClassMethod] -> [Ident]
visibleMethods = map methodName
errAmbiguousName :: QualIdent -> [ValueInfo] -> Message
errAmbiguousName x vs = errAmbiguous "name" x (map origName vs)
errAmbiguousType :: QualIdent -> [TypeInfo] -> Message
errAmbiguousType tc tcs = errAmbiguous "type" tc (map origName tcs)
errAmbiguous :: String -> QualIdent -> [QualIdent] -> Message
errAmbiguous what qn qns = posMessage qn
$ text "Ambiguous" <+> text what <+> text (escQualName qn)
$+$ text "It could refer to:"
$+$ nest 2 (vcat (map (text . escQualName) qns))
errModuleNotImported :: ModuleIdent -> Message
errModuleNotImported m = posMessage m $ hsep $ map text
["Module", escModuleName m, "not imported"]
errMultipleName :: [Ident] -> Message
errMultipleName = errMultiple "name"
errMultipleType :: [Ident] -> Message
errMultipleType = errMultiple "type"
errMultiple :: String -> [Ident] -> Message
errMultiple _ [] = internalError $
currentModuleName ++ ".errMultiple: empty list"
errMultiple what (i:is) = posMessage i $
text "Multiple exports of" <+> text what <+> text (escName i) <+> text "at:"
$+$ nest 2 (vcat (map showPos (i:is)))
where showPos = text . showLine . getPosition
errNonDataTypeOrTypeClass :: QualIdent -> Message
errNonDataTypeOrTypeClass tc = posMessage tc $ hsep $ map text
[escQualName tc, "is not a data type or type class"]
errOutsideTypeConstructor :: QualIdent -> QualIdent -> Message
errOutsideTypeConstructor c tc = errOutsideTypeExport "Data constructor" c tc
errOutsideTypeLabel :: QualIdent -> QualIdent -> Message
errOutsideTypeLabel l tc = errOutsideTypeExport "Label" l tc
errOutsideTypeExport :: String -> QualIdent -> QualIdent -> Message
errOutsideTypeExport what q tc = posMessage q
$ text what <+> text (escQualName q)
<+> text "outside type export in export list"
$+$ text "Use `" <> text (qualName tc) <+> parens (text (qualName q))
<> text "' instead"
errUndefinedElement :: QualIdent -> Ident -> Message
errUndefinedElement tc c = posMessage c $ hsep $ map text
[ escName c, "is not a constructor or label of type", escQualName tc ]
errUndefinedMethod :: QualIdent -> Ident -> Message
errUndefinedMethod cls f = posMessage f $ hsep $ map text
[ escName f, "is not a method of class", escQualName cls ]
errUndefinedName :: QualIdent -> Message
errUndefinedName = errUndefined "name"
errUndefinedTypeOrClass :: QualIdent -> Message
errUndefinedTypeOrClass = errUndefined "type or class"
errUndefined :: String -> QualIdent -> Message
errUndefined what tc = posMessage tc $ hsep $ map text
["Undefined", what, escQualName tc, "in export list"]