module Language.Haskell.Names.Imports
( importTable
, annotateImportDecls
)
where
import Data.Monoid
import Data.Maybe
import Data.Either
import Control.Monad.Writer
import Data.Map as Map (lookup)
import Language.Haskell.Exts (
Module(Module), ModuleName(ModuleName), ImportDecl(..),
ann,ImportSpecList(..),ImportSpec(..),Name(..),
Annotated)
import Language.Haskell.Exts.Extension (
Extension(DisableExtension), KnownExtension(ImplicitPrelude))
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 ((\\))
importTable :: Environment -> Module l -> Global.Table
importTable environment modul =
foldr Global.mergeTables perhapsPrelude tables where
tables = map (importDeclTable environment) importDecls
Module _ _ _ importDecls _ = modul
perhapsPrelude = if noImplicitPrelude
then Global.empty
else computeSymbolTable False preludeModuleName preludeSymbols
noImplicitPrelude =
DisableExtension ImplicitPrelude `elem` extensions || isPreludeImported
isPreludeImported = not (null (do
importDecl <- importDecls
guard (dropAnn (importModule importDecl) == preludeModuleName)))
preludeSymbols = fromMaybe [] (Map.lookup preludeModuleName environment)
(_, extensions) = getModuleExtensions modul
preludeModuleName :: ModuleName ()
preludeModuleName = ModuleName () "Prelude"
importDeclTable :: Environment -> ImportDecl l -> Global.Table
importDeclTable environment importDecl =
computeSymbolTable isQualified moduleName importSymbols where
ImportDecl _ importModuleName isQualified _ _ _ maybeAs maybeImportSpecList =
importDecl
moduleName = dropAnn (fromMaybe importModuleName maybeAs)
importSymbols = case maybeImportSpecList of
Nothing ->
importModuleSymbols
Just importSpecList ->
importSpecListSymbols importModuleName importModuleSymbols importSpecList
importModuleSymbols = fromMaybe [] (
Map.lookup (dropAnn importModuleName) environment)
importSpecListSymbols :: ModuleName l -> [Symbol] -> ImportSpecList l -> [Symbol]
importSpecListSymbols importModuleName allSymbols importSpecList =
if isHiding
then allSymbols \\ mentionedSymbols
else mentionedSymbols where
ImportSpecList _ isHiding importSpecs = importSpecList
annotatedImportSpecs =
map (resolveImportSpec importModuleName isHiding allSymbols) importSpecs
mentionedSymbols =
mconcat (rights (map ann2syms annotatedImportSpecs))
annotateImportDecls ::
ModuleName l -> Environment -> [ImportDecl l] -> [ImportDecl (Scoped l)]
annotateImportDecls moduleName environment importDecls =
map (annotateImportDecl moduleName environment) importDecls
annotateImportDecl ::
ModuleName l -> Environment -> ImportDecl l -> ImportDecl (Scoped l)
annotateImportDecl moduleName environment importDecl = importDecl' where
ImportDecl
l
importModuleName
isQualified
isSource
isSafe
importPackage
maybeAs
maybeImportSpecList = importDecl
importDecl' = case Map.lookup (dropAnn importModuleName) environment of
Nothing -> scopeError (EModNotFound importModuleName) importDecl
Just symbols ->
ImportDecl
l'
importModuleName'
isQualified
isSource
isSafe
importPackage
maybeAs'
maybeImportSpecList' where
l' = Scoped info l
importModuleName' = fmap (Scoped (ImportPart symbols)) importModuleName
maybeAs' = fmap noScope maybeAs
maybeImportSpecList' =
fmap (annotateImportSpecList moduleName symbols) maybeImportSpecList
info = case maybeImportSpecList' of
Just sl | Scoped (ScopeError e) _ <- ann sl ->
ScopeError e
_ -> Import table
table = computeSymbolTable isQualified qualificationName importSymbols
qualificationName = dropAnn (fromMaybe importModuleName maybeAs)
importSymbols =
maybe
symbols
(importSpecListSymbols moduleName symbols)
maybeImportSpecList
annotateImportSpecList ::
ModuleName l -> [Symbol] -> ImportSpecList l -> ImportSpecList (Scoped l)
annotateImportSpecList moduleName allSymbols importSpecList =
(ImportSpecList l' isHiding importSpecs') where
ImportSpecList l isHiding importSpecs = importSpecList
l' = Scoped (ImportPart importSymbols) l
importSpecs' = map (resolveImportSpec moduleName isHiding allSymbols) importSpecs
importSymbols = importSpecListSymbols moduleName allSymbols importSpecList
resolveImportSpec
:: ModuleName l
-> Bool
-> [Symbol]
-> ImportSpec l
-> ImportSpec (Scoped l)
resolveImportSpec mod isHiding symbols spec =
case spec of
IVar _ n ->
let
matches =
[ symbol
| symbol <- symbols
, not (isConstructor symbol)
, symbol ~~ n]
in
checkUnique
(ENotExported Nothing n mod)
matches
spec
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 == dropAnn 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