src/Importify/Syntax/Import.hs
-- | Haskell AST utilities to work with @import@ section.
module Importify.Syntax.Import
( getImportModuleName
, importNamesWithTables
, importSlice
, isImportImplicit
, switchHidingImports
) where
import Universum
import qualified Data.List.NonEmpty as NE
import Language.Haskell.Exts (Extension (DisableExtension),
ImportDecl (..),
ImportSpecList (..),
KnownExtension (ImplicitPrelude),
Module (..), ModuleName,
ModuleName (..),
ModulePragma (LanguagePragma),
Name (Ident), SrcSpan (..),
SrcSpanInfo (..), combSpanInfo,
noSrcSpan, prettyExtension)
import Language.Haskell.Names (NameInfo (Import))
import Language.Haskell.Names.GlobalSymbolTable (Table)
import Importify.Syntax.Module (isInsideExport)
import Importify.Syntax.Scoped (InScoped, pullScopedInfo)
-- | Returns module name for 'ImportDecl' with annotation erased.
getImportModuleName :: ImportDecl l -> ModuleName ()
getImportModuleName ImportDecl{..} = () <$ importModule
-- | Returns 'True' iff import has next form:
-- @
-- import Module.Name
-- import Module.Name as Other.Name
-- import Module.Name hiding (smth)
-- @
isImportImplicit :: ImportDecl l -> Bool
isImportImplicit ImportDecl{ importQualified = True } = False
isImportImplicit ImportDecl{ importSpecs = Nothing } = True
isImportImplicit ImportDecl{ importSpecs = Just (ImportSpecList _ True _) } = True
isImportImplicit _ = False
-- | This function returns name of import disregard to its
-- qualification, how this module should be referenced, i.e. like this:
-- @
-- import <whatever> A ⇒ A
-- import <whatever> B as X ⇒ X
-- @
importReferenceName :: ImportDecl l -> ModuleName ()
importReferenceName ImportDecl{ importAs = Nothing, .. } = () <$ importModule
importReferenceName ImportDecl{ importAs = Just name } = () <$ name
-- | Keep only hiding imports making them non-hiding. This function
-- needed to collect unused hiding imports because @importTable@ doesn't
-- track information about hiding imports.
switchHidingImports :: Module SrcSpanInfo -> Module SrcSpanInfo
switchHidingImports (Module ml mhead mpragmas mimports mdecls) =
Module ml
mhead
( LanguagePragma noSrcSpan
[ Ident noSrcSpan
$ prettyExtension
$ DisableExtension ImplicitPrelude
]
: mpragmas)
(mapMaybe unhide mimports)
mdecls
where
unhide :: ImportDecl SrcSpanInfo -> Maybe (ImportDecl SrcSpanInfo)
unhide decl = do
ImportSpecList l isHiding imports <- importSpecs decl
guard isHiding
guard $ not $ isInsideExport mhead (importReferenceName decl)
pure $ decl { importSpecs = Just $ ImportSpecList l False imports }
switchHidingImports m = m
-- | Collect mapping from import name to to list of symbols it exports.
importNamesWithTables :: [InScoped ImportDecl] -> [(ModuleName (), Table)]
importNamesWithTables = map (getImportModuleName &&& getImportedSymbols)
where
getImportedSymbols :: InScoped ImportDecl -> Table
getImportedSymbols = fromImportInfo . pullScopedInfo
fromImportInfo :: NameInfo l -> Table
fromImportInfo (Import dict) = dict
fromImportInfo _ = mempty
-- | Returns pair of line numbers — first and last line of import section
-- if any import is in list.
importSlice :: [ImportDecl SrcSpanInfo] -> Maybe (Int, Int)
importSlice [] = Nothing
importSlice [ImportDecl{..}] = Just $ startAndEndLines importAnn
importSlice (x:y:xs) = Just $ startAndEndLines
$ combSpanInfo (importAnn x) (importAnn $ NE.last (y :| xs))
startAndEndLines :: SrcSpanInfo -> (Int, Int)
startAndEndLines (SrcSpanInfo SrcSpan{..} _) = (srcSpanStartLine, srcSpanEndLine)