module Language.Haskell.Names.Imports
( processImport
, processImports
)
where
import qualified Data.Set as Set
import Data.Monoid
import Data.Maybe
import Data.Either
import Data.Foldable (fold)
import Control.Applicative
import Control.Arrow
import Control.Monad.Writer
import Distribution.HaskellSuite.Modules
import qualified Language.Haskell.Exts as UnAnn (ModuleName(ModuleName))
import Language.Haskell.Exts.Annotated.Simplify (sName,sModuleName)
import Language.Haskell.Exts.Annotated (
ModuleName(ModuleName),ImportDecl(..),KnownExtension(ImplicitPrelude),
ann,ImportSpecList(..),ImportSpec(..),Name(..),
Annotated,Namespace(NoNamespace,TypeNamespace))
import Language.Haskell.Names.Types
import Language.Haskell.Names.ScopeUtils
import qualified Language.Haskell.Names.GlobalSymbolTable as Global
import Language.Haskell.Names.SyntaxUtils
import Data.List ((\\))
instance ModName (ModuleName l) where
modToString (ModuleName _ s) = s
preludeName :: String
preludeName = "Prelude"
processImports
:: (MonadModule m, ModuleInfo m ~ [Symbol])
=> ExtensionSet
-> [ImportDecl l]
-> m ([ImportDecl (Scoped l)], Global.Table)
processImports exts importDecls = do
(annotated,tables) <- mapM processImport importDecls >>= return . unzip
let tbl = foldr Global.mergeTables Global.empty tables
let
isPreludeImported = not . null $
[ () | ImportDecl { importModule = ModuleName _ modName } <- importDecls
, modName == preludeName ]
importPrelude =
ImplicitPrelude `Set.member` exts &&
not isPreludeImported
tbl' <-
if not importPrelude
then return tbl
else do
syms <- fold `liftM` getModuleInfo preludeName
return $ Global.mergeTables tbl (computeSymbolTable
False
(UnAnn.ModuleName preludeName)
syms)
return (annotated, tbl')
processImport
:: (MonadModule m, ModuleInfo m ~ [Symbol])
=> ImportDecl l
-> m (ImportDecl (Scoped l), Global.Table)
processImport imp = do
mbi <- getModuleInfo (importModule imp)
case mbi of
Nothing ->
let e = EModNotFound (importModule imp)
in return (scopeError e imp, Global.empty)
Just syms -> return $ resolveImportDecl syms imp
resolveImportDecl
:: [Symbol]
-> ImportDecl l
-> (ImportDecl (Scoped l), Global.Table)
resolveImportDecl syms (ImportDecl l mod qual src impSafe pkg mbAs mbSpecList) =
let
(mbSpecList', impSyms) =
(fmap fst &&& maybe syms snd) $
resolveImportSpecList mod syms <$> mbSpecList
tbl = computeSymbolTable qual (sModuleName (fromMaybe mod mbAs)) impSyms
info =
case mbSpecList' of
Just sl | Scoped (ScopeError e) _ <- ann sl ->
ScopeError e
_ -> Import tbl
in
(ImportDecl
(Scoped info l)
(Scoped (ImportPart syms) <$> mod)
qual
src
impSafe
pkg
(fmap noScope mbAs)
mbSpecList'
, tbl)
resolveImportSpecList
:: ModuleName l
-> [Symbol]
-> ImportSpecList l
-> (ImportSpecList (Scoped l), [Symbol])
resolveImportSpecList mod allSyms (ImportSpecList l isHiding specs) =
let specs' = map (resolveImportSpec mod isHiding allSyms) specs
mentionedSyms = mconcat $ rights $ map ann2syms specs'
importedSyms = computeImportedSymbols isHiding allSyms mentionedSyms
newAnn = Scoped (ImportPart importedSyms) l
in
(ImportSpecList newAnn isHiding specs', importedSyms)
computeImportedSymbols
:: Bool
-> [Symbol]
-> [Symbol]
-> [Symbol]
computeImportedSymbols isHiding allSymbols mentionedSymbols =
case isHiding of
False -> mentionedSymbols
True -> allSymbols \\ mentionedSymbols
resolveImportSpec
:: ModuleName l
-> Bool
-> [Symbol]
-> ImportSpec l
-> ImportSpec (Scoped l)
resolveImportSpec mod isHiding symbols spec =
case spec of
IVar _ (NoNamespace {}) n ->
let
matches =
[ symbol
| symbol <- symbols
, not (isConstructor symbol)
, symbol ~~ n]
in
checkUnique
(ENotExported Nothing n mod)
matches
spec
IVar _ (TypeNamespace {}) _ -> error "'type' namespace is not supported yet"
IAbs _ n
| isHiding ->
let
matches = [ symbol | symbol <- symbols, symbol ~~ n]
in
if null matches
then
scopeError (ENotExported Nothing n mod) spec
else
Scoped (ImportPart matches) <$> spec
| otherwise ->
let
matches = [symbol | symbol <- symbols, symbol ~~ n, not (isConstructor symbol)]
in
checkUnique
(ENotExported Nothing n mod)
matches
spec
IThingAll l n ->
let
matches = [ symbol | symbol <- symbols, symbol ~~ n, hasSubImports symbol]
subs = [ symbol
| n <- matches
, symbol <- symbols
, Just n' <- return $ symbolParent symbol
, n' == symbolName n ]
n' =
checkUnique
(ENotExported Nothing n mod)
matches
n
in
case ann n' of
e@(Scoped ScopeError{} _) -> IThingAll e n'
_ ->
IThingAll
(Scoped
(ImportPart (subs <> matches))
l
)
n'
IThingWith l n cns ->
let
matches = [symbol | symbol <- symbols, symbol ~~ n, hasSubImports symbol]
n' =
checkUnique
(ENotExported Nothing n mod)
matches
n
typeName = symbolName $ head matches
(cns', cnSyms) =
resolveCNames
symbols
typeName
(\cn -> ENotExported (Just n) (unCName cn) mod)
cns
in
IThingWith
(Scoped
(ImportPart (cnSyms <> matches))
l
)
n'
cns'
where
(~~) :: Symbol -> Name l -> Bool
symbol ~~ name = symbolName symbol == sName name
isConstructor :: Symbol -> Bool
isConstructor Constructor {} = True
isConstructor _ = False
hasSubImports :: Symbol -> Bool
hasSubImports symbol = case symbol of
Data {} -> True
NewType {} -> True
DataFam {} -> True
Class {} -> True
_ -> False
ann2syms :: Annotated a => a (Scoped l) -> Either (Error l) ([Symbol])
ann2syms a =
case ann a of
Scoped (ScopeError e) _ -> Left e
Scoped (ImportPart syms) _ -> Right syms
_ -> Left $ EInternal "ann2syms"
checkUnique
:: Functor f =>
Error l ->
[Symbol] ->
f l ->
f (Scoped l)
checkUnique notFound symbols f =
case length symbols of
0 -> scopeError notFound f
1 -> Scoped (ImportPart symbols) <$> f
_ -> scopeError (EInternal ("ambiguous import: " ++ show symbols)) f