{-# language ApplicativeDo #-}
{-# language BlockArguments #-}
{-# language FlexibleContexts #-}
{-# language LambdaCase #-}
{-# language NamedFieldPuns #-}
{-# language OverloadedStrings #-}

-- | This module provides an entry point to the Weeder executable.

module Weeder.Main ( main, mainWithConfig ) where

-- base
import Control.Monad ( guard, unless, when )
import Control.Monad.IO.Class ( liftIO )
import Data.Bool
import Data.Foldable
import Data.List ( isSuffixOf )
import Data.Version ( showVersion )
import System.Exit ( exitFailure )

-- containers
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set

-- text
import qualified Data.Text as T

-- dhall
import qualified Dhall

-- directory
import System.Directory ( canonicalizePath, doesDirectoryExist, doesFileExist, doesPathExist, listDirectory, withCurrentDirectory )

-- filepath
import System.FilePath ( isExtensionOf )

-- ghc
import HieBin ( HieFileResult( HieFileResult, hie_file_result ), readHieFileWithVersion )
import HieTypes ( HieFile( hie_hs_file ), hieVersion )
import Module ( moduleName, moduleNameString )
import NameCache ( initNameCache, NameCache )
import OccName ( occNameString )
import SrcLoc ( RealSrcLoc, realSrcSpanStart, srcLocLine )
import UniqSupply ( mkSplitUniqSupply )

-- regex-tdfa
import Text.Regex.TDFA ( (=~) )

-- optparse-applicative
import Options.Applicative

-- transformers
import Control.Monad.Trans.State.Strict ( execStateT )

-- weeder
import Weeder
import Weeder.Config
import Paths_weeder (version)


-- | Parse command line arguments and into a 'Config' and run 'mainWithConfig'.
main :: IO ()
main :: IO ()
main = do
  (Text
configExpr, String
hieExt, [String]
hieDirectories, Bool
requireHsFiles) <-
    ParserInfo (Text, String, [String], Bool)
-> IO (Text, String, [String], Bool)
forall a. ParserInfo a -> IO a
execParser (ParserInfo (Text, String, [String], Bool)
 -> IO (Text, String, [String], Bool))
-> ParserInfo (Text, String, [String], Bool)
-> IO (Text, String, [String], Bool)
forall a b. (a -> b) -> a -> b
$
      Parser (Text, String, [String], Bool)
-> InfoMod (Text, String, [String], Bool)
-> ParserInfo (Text, String, [String], Bool)
forall a. Parser a -> InfoMod a -> ParserInfo a
info (Parser (Text, String, [String], Bool)
optsP Parser (Text, String, [String], Bool)
-> Parser
     ((Text, String, [String], Bool) -> (Text, String, [String], Bool))
-> Parser (Text, String, [String], Bool)
forall (f :: * -> *) a b. Applicative f => f a -> f (a -> b) -> f b
<**> Parser
  ((Text, String, [String], Bool) -> (Text, String, [String], Bool))
forall a. Parser (a -> a)
helper Parser (Text, String, [String], Bool)
-> Parser
     ((Text, String, [String], Bool) -> (Text, String, [String], Bool))
-> Parser (Text, String, [String], Bool)
forall (f :: * -> *) a b. Applicative f => f a -> f (a -> b) -> f b
<**> Parser
  ((Text, String, [String], Bool) -> (Text, String, [String], Bool))
forall a. Parser (a -> a)
versionP) InfoMod (Text, String, [String], Bool)
forall a. Monoid a => a
mempty

  Decoder Config -> Text -> IO Config
forall a. Decoder a -> Text -> IO a
Dhall.input Decoder Config
config Text
configExpr
    IO Config -> (Config -> IO ()) -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= String -> [String] -> Bool -> Config -> IO ()
mainWithConfig String
hieExt [String]
hieDirectories Bool
requireHsFiles
  where
    optsP :: Parser (Text, String, [String], Bool)
optsP = (,,,)
        (Text
 -> String -> [String] -> Bool -> (Text, String, [String], Bool))
-> Parser Text
-> Parser
     (String -> [String] -> Bool -> (Text, String, [String], Bool))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Mod OptionFields Text -> Parser Text
forall s. IsString s => Mod OptionFields s -> Parser s
strOption
            ( String -> Mod OptionFields Text
forall (f :: * -> *) a. HasName f => String -> Mod f a
long String
"config"
                Mod OptionFields Text
-> Mod OptionFields Text -> Mod OptionFields Text
forall a. Semigroup a => a -> a -> a
<> String -> Mod OptionFields Text
forall (f :: * -> *) a. String -> Mod f a
help String
"A Dhall expression for Weeder's configuration. Can either be a file path (a Dhall import) or a literal Dhall expression."
                Mod OptionFields Text
-> Mod OptionFields Text -> Mod OptionFields Text
forall a. Semigroup a => a -> a -> a
<> Text -> Mod OptionFields Text
forall (f :: * -> *) a. HasValue f => a -> Mod f a
value Text
"./weeder.dhall"
                Mod OptionFields Text
-> Mod OptionFields Text -> Mod OptionFields Text
forall a. Semigroup a => a -> a -> a
<> String -> Mod OptionFields Text
forall (f :: * -> *) a. HasMetavar f => String -> Mod f a
metavar String
"<weeder.dhall>"
                Mod OptionFields Text
-> Mod OptionFields Text -> Mod OptionFields Text
forall a. Semigroup a => a -> a -> a
<> (Text -> String) -> Mod OptionFields Text
forall a (f :: * -> *). (a -> String) -> Mod f a
showDefaultWith Text -> String
T.unpack
            )
        Parser
  (String -> [String] -> Bool -> (Text, String, [String], Bool))
-> Parser String
-> Parser ([String] -> Bool -> (Text, String, [String], Bool))
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Mod OptionFields String -> Parser String
forall s. IsString s => Mod OptionFields s -> Parser s
strOption
            ( String -> Mod OptionFields String
forall (f :: * -> *) a. HasName f => String -> Mod f a
long String
"hie-extension"
                Mod OptionFields String
-> Mod OptionFields String -> Mod OptionFields String
forall a. Semigroup a => a -> a -> a
<> String -> Mod OptionFields String
forall (f :: * -> *) a. HasValue f => a -> Mod f a
value String
".hie"
                Mod OptionFields String
-> Mod OptionFields String -> Mod OptionFields String
forall a. Semigroup a => a -> a -> a
<> String -> Mod OptionFields String
forall (f :: * -> *) a. String -> Mod f a
help String
"Extension of HIE files"
                Mod OptionFields String
-> Mod OptionFields String -> Mod OptionFields String
forall a. Semigroup a => a -> a -> a
<> Mod OptionFields String
forall a (f :: * -> *). Show a => Mod f a
showDefault
            )
        Parser ([String] -> Bool -> (Text, String, [String], Bool))
-> Parser [String]
-> Parser (Bool -> (Text, String, [String], Bool))
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Parser String -> Parser [String]
forall (f :: * -> *) a. Alternative f => f a -> f [a]
many (
            Mod OptionFields String -> Parser String
forall s. IsString s => Mod OptionFields s -> Parser s
strOption
                ( String -> Mod OptionFields String
forall (f :: * -> *) a. HasName f => String -> Mod f a
long String
"hie-directory"
                    Mod OptionFields String
-> Mod OptionFields String -> Mod OptionFields String
forall a. Semigroup a => a -> a -> a
<> String -> Mod OptionFields String
forall (f :: * -> *) a. String -> Mod f a
help String
"A directory to look for .hie files in. Maybe specified multiple times. Default ./."
                )
            )
        Parser (Bool -> (Text, String, [String], Bool))
-> Parser Bool -> Parser (Text, String, [String], Bool)
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Mod FlagFields Bool -> Parser Bool
switch
              ( String -> Mod FlagFields Bool
forall (f :: * -> *) a. HasName f => String -> Mod f a
long String
"require-hs-files"
                  Mod FlagFields Bool -> Mod FlagFields Bool -> Mod FlagFields Bool
forall a. Semigroup a => a -> a -> a
<> String -> Mod FlagFields Bool
forall (f :: * -> *) a. String -> Mod f a
help String
"Requries that all .hie files have matching .hs files. This can help deal with skipping .hie files for Haskell modules that have since been removed"
              )

    versionP :: Parser (a -> a)
versionP = String -> Mod OptionFields (a -> a) -> Parser (a -> a)
forall a. String -> Mod OptionFields (a -> a) -> Parser (a -> a)
infoOption ( String
"weeder version "
                            String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Version -> String
showVersion Version
version
                            String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"\nhie version "
                            String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Integer -> String
forall a. Show a => a -> String
show Integer
hieVersion )
        ( String -> Mod OptionFields (a -> a)
forall (f :: * -> *) a. HasName f => String -> Mod f a
long String
"version" Mod OptionFields (a -> a)
-> Mod OptionFields (a -> a) -> Mod OptionFields (a -> a)
forall a. Semigroup a => a -> a -> a
<> String -> Mod OptionFields (a -> a)
forall (f :: * -> *) a. String -> Mod f a
help String
"Show version" )


-- | Run Weeder in the current working directory with a given 'Config'.
--
-- This will recursively find all files with the given extension in the given directories, perform
-- analysis, and report all unused definitions according to the 'Config'.
mainWithConfig :: String -> [FilePath] -> Bool -> Config -> IO ()
mainWithConfig :: String -> [String] -> Bool -> Config -> IO ()
mainWithConfig String
hieExt [String]
hieDirectories Bool
requireHsFiles Config{ Set String
rootPatterns :: Config -> Set String
rootPatterns :: Set String
rootPatterns, Bool
typeClassRoots :: Config -> Bool
typeClassRoots :: Bool
typeClassRoots } = do
  [String]
hieFilePaths <-
    [[String]] -> [String]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([[String]] -> [String]) -> IO [[String]] -> IO [String]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$>
      (String -> IO [String]) -> [String] -> IO [[String]]
forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
(a -> f b) -> t a -> f (t b)
traverse ( String -> String -> IO [String]
getFilesIn String
hieExt )
        ( if [String] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [String]
hieDirectories
          then [String
"./."]
          else [String]
hieDirectories
        )

  [String]
hsFilePaths <-
    if Bool
requireHsFiles
      then String -> String -> IO [String]
getFilesIn String
".hs" String
"./."
      else [String] -> IO [String]
forall (f :: * -> *) a. Applicative f => a -> f a
pure []

  NameCache
nameCache <- do
    UniqSupply
uniqSupply <- Char -> IO UniqSupply
mkSplitUniqSupply Char
'z'
    return ( UniqSupply -> [Name] -> NameCache
initNameCache UniqSupply
uniqSupply [] )

  Analysis
analysis <-
    (StateT Analysis IO () -> Analysis -> IO Analysis)
-> Analysis -> StateT Analysis IO () -> IO Analysis
forall a b c. (a -> b -> c) -> b -> a -> c
flip StateT Analysis IO () -> Analysis -> IO Analysis
forall (m :: * -> *) s a. Monad m => StateT s m a -> s -> m s
execStateT Analysis
emptyAnalysis do
      [String]
-> (String -> StateT Analysis IO ()) -> StateT Analysis IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
t a -> (a -> f b) -> f ()
for_ [String]
hieFilePaths \String
hieFilePath -> do
        HieFile
hieFileResult <- IO HieFile -> StateT Analysis IO HieFile
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO ( NameCache -> String -> IO HieFile
readCompatibleHieFileOrExit NameCache
nameCache String
hieFilePath )
        let hsFileExists :: Bool
hsFileExists = (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any ( HieFile -> String
hie_hs_file HieFile
hieFileResult String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` ) [String]
hsFilePaths
        Bool -> StateT Analysis IO () -> StateT Analysis IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool
requireHsFiles Bool -> Bool -> Bool
==> Bool
hsFileExists) do
          HieFile -> StateT Analysis IO ()
forall (m :: * -> *). MonadState Analysis m => HieFile -> m ()
analyseHieFile HieFile
hieFileResult

  let
    roots :: Set Declaration
roots =
      (Declaration -> Bool) -> Set Declaration -> Set Declaration
forall a. (a -> Bool) -> Set a -> Set a
Set.filter
        ( \Declaration
d ->
            (String -> Bool) -> Set String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any
              ( ( ModuleName -> String
moduleNameString ( Module -> ModuleName
moduleName ( Declaration -> Module
declModule Declaration
d ) ) String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"." String -> String -> String
forall a. Semigroup a => a -> a -> a
<> OccName -> String
occNameString ( Declaration -> OccName
declOccName Declaration
d ) ) String -> String -> Bool
forall source source1 target.
(RegexMaker Regex CompOption ExecOption source,
 RegexContext Regex source1 target) =>
source1 -> source -> target
=~ )
              Set String
rootPatterns
        )
        ( Analysis -> Set Declaration
allDeclarations Analysis
analysis )

    reachableSet :: Set Declaration
reachableSet =
      Analysis -> Set Root -> Set Declaration
reachable
        Analysis
analysis
        ( (Declaration -> Root) -> Set Declaration -> Set Root
forall b a. Ord b => (a -> b) -> Set a -> Set b
Set.map Declaration -> Root
DeclarationRoot Set Declaration
roots Set Root -> Set Root -> Set Root
forall a. Semigroup a => a -> a -> a
<> Set Root -> Set Root -> Bool -> Set Root
forall a. a -> a -> Bool -> a
bool Set Root
forall a. Monoid a => a
mempty ( (Declaration -> Root) -> Set Declaration -> Set Root
forall b a. Ord b => (a -> b) -> Set a -> Set b
Set.map Declaration -> Root
DeclarationRoot ( Analysis -> Set Declaration
implicitRoots Analysis
analysis ) ) Bool
typeClassRoots )

    dead :: Set Declaration
dead =
      Analysis -> Set Declaration
allDeclarations Analysis
analysis Set Declaration -> Set Declaration -> Set Declaration
forall a. Ord a => Set a -> Set a -> Set a
Set.\\ Set Declaration
reachableSet

    warnings :: Map String [(RealSrcLoc, Declaration)]
warnings =
      ([(RealSrcLoc, Declaration)]
 -> [(RealSrcLoc, Declaration)] -> [(RealSrcLoc, Declaration)])
-> [Map String [(RealSrcLoc, Declaration)]]
-> Map String [(RealSrcLoc, Declaration)]
forall (f :: * -> *) k a.
(Foldable f, Ord k) =>
(a -> a -> a) -> f (Map k a) -> Map k a
Map.unionsWith [(RealSrcLoc, Declaration)]
-> [(RealSrcLoc, Declaration)] -> [(RealSrcLoc, Declaration)]
forall a. [a] -> [a] -> [a]
(++) ([Map String [(RealSrcLoc, Declaration)]]
 -> Map String [(RealSrcLoc, Declaration)])
-> [Map String [(RealSrcLoc, Declaration)]]
-> Map String [(RealSrcLoc, Declaration)]
forall a b. (a -> b) -> a -> b
$
      (Declaration -> [Map String [(RealSrcLoc, Declaration)]])
-> Set Declaration -> [Map String [(RealSrcLoc, Declaration)]]
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap
        ( \Declaration
d ->
            Maybe [Map String [(RealSrcLoc, Declaration)]]
-> [Map String [(RealSrcLoc, Declaration)]]
forall (t :: * -> *) m. (Foldable t, Monoid m) => t m -> m
fold (Maybe [Map String [(RealSrcLoc, Declaration)]]
 -> [Map String [(RealSrcLoc, Declaration)]])
-> Maybe [Map String [(RealSrcLoc, Declaration)]]
-> [Map String [(RealSrcLoc, Declaration)]]
forall a b. (a -> b) -> a -> b
$ do
              String
moduleFilePath <- Module -> Map Module String -> Maybe String
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup ( Declaration -> Module
declModule Declaration
d ) ( Analysis -> Map Module String
modulePaths Analysis
analysis )
              Set RealSrcSpan
spans <- Declaration
-> Map Declaration (Set RealSrcSpan) -> Maybe (Set RealSrcSpan)
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Declaration
d ( Analysis -> Map Declaration (Set RealSrcSpan)
declarationSites Analysis
analysis )
              Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Set RealSrcSpan -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null Set RealSrcSpan
spans
              let starts :: [RealSrcLoc]
starts = (RealSrcSpan -> RealSrcLoc) -> [RealSrcSpan] -> [RealSrcLoc]
forall a b. (a -> b) -> [a] -> [b]
map RealSrcSpan -> RealSrcLoc
realSrcSpanStart ([RealSrcSpan] -> [RealSrcLoc]) -> [RealSrcSpan] -> [RealSrcLoc]
forall a b. (a -> b) -> a -> b
$ Set RealSrcSpan -> [RealSrcSpan]
forall a. Set a -> [a]
Set.toList Set RealSrcSpan
spans
              return [ String
-> [(RealSrcLoc, Declaration)]
-> Map String [(RealSrcLoc, Declaration)]
forall k a. k -> a -> Map k a
Map.singleton String
moduleFilePath ( (RealSrcLoc -> Declaration -> (RealSrcLoc, Declaration))
-> [RealSrcLoc] -> [Declaration] -> [(RealSrcLoc, Declaration)]
forall (f :: * -> *) a b c.
Applicative f =>
(a -> b -> c) -> f a -> f b -> f c
liftA2 (,) [RealSrcLoc]
starts (Declaration -> [Declaration]
forall (f :: * -> *) a. Applicative f => a -> f a
pure Declaration
d) ) ]
        )
        Set Declaration
dead

  [(String, [(RealSrcLoc, Declaration)])]
-> ((String, [(RealSrcLoc, Declaration)]) -> IO ()) -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
t a -> (a -> f b) -> f ()
for_ ( Map String [(RealSrcLoc, Declaration)]
-> [(String, [(RealSrcLoc, Declaration)])]
forall k a. Map k a -> [(k, a)]
Map.toList Map String [(RealSrcLoc, Declaration)]
warnings ) \( String
path, [(RealSrcLoc, Declaration)]
declarations ) ->
    [(RealSrcLoc, Declaration)]
-> ((RealSrcLoc, Declaration) -> IO ()) -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
t a -> (a -> f b) -> f ()
for_ [(RealSrcLoc, Declaration)]
declarations \( RealSrcLoc
start, Declaration
d ) ->
      String -> IO ()
putStrLn (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String -> RealSrcLoc -> Declaration -> String
showWeed String
path RealSrcLoc
start Declaration
d

  Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ( Map String [(RealSrcLoc, Declaration)] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null Map String [(RealSrcLoc, Declaration)]
warnings ) IO ()
forall a. IO a
exitFailure

showWeed :: FilePath -> RealSrcLoc -> Declaration -> String
showWeed :: String -> RealSrcLoc -> Declaration -> String
showWeed String
path RealSrcLoc
start Declaration
d =
  String
path String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
":" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Int -> String
forall a. Show a => a -> String
show ( RealSrcLoc -> Int
srcLocLine RealSrcLoc
start ) String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
": "
    String -> String -> String
forall a. Semigroup a => a -> a -> a
<> OccName -> String
occNameString ( Declaration -> OccName
declOccName Declaration
d)


-- | Recursively search for files with the given extension in given directory
getFilesIn
  :: String
  -- ^ Only files with this extension are considered
  -> FilePath
  -- ^ Directory to look in
  -> IO [FilePath]
getFilesIn :: String -> String -> IO [String]
getFilesIn String
ext String
path = do
  Bool
exists <-
    String -> IO Bool
doesPathExist String
path

  if Bool
exists
    then do
      Bool
isFile <-
        String -> IO Bool
doesFileExist String
path

      if Bool
isFile Bool -> Bool -> Bool
&& String
ext String -> String -> Bool
`isExtensionOf` String
path
        then do
          String
path' <-
            String -> IO String
canonicalizePath String
path

          return [ String
path' ]

        else do
          Bool
isDir <-
            String -> IO Bool
doesDirectoryExist String
path

          if Bool
isDir
            then do
              [String]
cnts <-
                String -> IO [String]
listDirectory String
path

              String -> IO [String] -> IO [String]
forall a. String -> IO a -> IO a
withCurrentDirectory String
path ( (String -> IO [String]) -> [String] -> IO [String]
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap ( String -> String -> IO [String]
getFilesIn String
ext ) [String]
cnts )

            else
              [String] -> IO [String]
forall (m :: * -> *) a. Monad m => a -> m a
return []

    else
      [String] -> IO [String]
forall (m :: * -> *) a. Monad m => a -> m a
return []


-- | Read a .hie file, exiting if it's an incompatible version.
readCompatibleHieFileOrExit :: NameCache -> FilePath -> IO HieFile
readCompatibleHieFileOrExit :: NameCache -> String -> IO HieFile
readCompatibleHieFileOrExit NameCache
nameCache String
path = do
  Either HieHeader (HieFileResult, NameCache)
res <- (HieHeader -> Bool)
-> NameCache
-> String
-> IO (Either HieHeader (HieFileResult, NameCache))
readHieFileWithVersion (\ (Integer
v, ByteString
_) -> Integer
v Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
hieVersion) NameCache
nameCache String
path
  case Either HieHeader (HieFileResult, NameCache)
res of
    Right ( HieFileResult{ HieFile
hie_file_result :: HieFile
hie_file_result :: HieFileResult -> HieFile
hie_file_result }, NameCache
_ ) ->
      HieFile -> IO HieFile
forall (m :: * -> *) a. Monad m => a -> m a
return HieFile
hie_file_result
    Left ( Integer
v, ByteString
_ghcVersion ) -> do
      String -> IO ()
putStrLn (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"incompatible hie file: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
path
      String -> IO ()
putStrLn (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"    this version of weeder was compiled with GHC version "
               String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Integer -> String
forall a. Show a => a -> String
show Integer
hieVersion
      String -> IO ()
putStrLn (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"    the hie files in this project were generated with GHC version "
               String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Integer -> String
forall a. Show a => a -> String
show Integer
v
      String -> IO ()
putStrLn (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"    weeder must be built with the same GHC version"
               String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
" as the project it is used on"
      IO HieFile
forall a. IO a
exitFailure



infixr 5 ==>


-- | An infix operator for logical implication
(==>) :: Bool -> Bool -> Bool
Bool
True  ==> :: Bool -> Bool -> Bool
==> Bool
x = Bool
x
Bool
False ==> Bool
_ = Bool
True