liquidhaskell-boot-0.9.6.3: src-ghc/Liquid/GHC/API/Extra.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE RankNTypes #-}
module Liquid.GHC.API.Extra (
module StableModule
, ApiComment(..)
, apiComments
, apiCommentsParsedSource
, dataConSig
, desugarModuleIO
, fsToUnitId
, isPatErrorAlt
, lookupModSummary
, modInfoLookupNameIO
, moduleInfoTc
, parseModuleIO
, qualifiedNameFS
, relevantModules
, renderWithStyle
, showPprQualified
, showSDocQualified
, strictNothing
, thisPackage
, tyConRealArity
, typecheckModuleIO
) where
import Control.Monad.IO.Class
import Liquid.GHC.API.StableModule as StableModule
import GHC
import Data.Data (Data, gmapQr)
import Data.Generics (extQ)
import Data.Foldable (asum)
import Data.List (foldl', sortOn)
import qualified Data.Map as Map
import qualified Data.Set as S
import GHC.Core as Ghc
import GHC.Core.Coercion as Ghc
import GHC.Core.DataCon as Ghc
import GHC.Core.Make (pAT_ERROR_ID)
import GHC.Core.Type as Ghc hiding (typeKind , isPredTy, extendCvSubst, linear)
import GHC.Data.FastString as Ghc
import qualified GHC.Data.EnumSet as EnumSet
import GHC.Data.Maybe
import qualified GHC.Data.Strict
import GHC.Driver.Env
import GHC.Driver.Main
import GHC.Driver.Session as Ghc
import GHC.Tc.Types
import GHC.Types.Name (isSystemName, nameModule_maybe, occNameFS)
import GHC.Types.SrcLoc as Ghc
import GHC.Types.TypeEnv
import GHC.Types.Unique (getUnique)
import GHC.Types.Unique.FM
import GHC.Unit.Module.Deps as Ghc (Dependencies(dep_direct_mods))
import GHC.Unit.Module.Graph as Ghc
( NodeKey(NodeKey_Module)
, ModNodeKeyWithUid(ModNodeKeyWithUid)
, mgTransDeps
)
import GHC.Unit.Module.ModDetails (md_types)
import GHC.Unit.Module.ModSummary (isBootSummary)
import GHC.Utils.Outputable as Ghc hiding ((<>))
import GHC.Unit.Module
import GHC.Unit.Module.ModGuts
import GHC.Unit.Module.Deps (Usage(..))
-- 'fsToUnitId' is gone in GHC 9, but we can bring code it in terms of 'fsToUnit' and 'toUnitId'.
fsToUnitId :: FastString -> UnitId
fsToUnitId = toUnitId . fsToUnit
thisPackage :: DynFlags -> UnitId
thisPackage = homeUnitId_
-- See NOTE [tyConRealArity].
tyConRealArity :: TyCon -> Int
tyConRealArity tc = go 0 (tyConKind tc)
where
go :: Int -> Kind -> Int
go !acc k =
case asum [fmap (\(_, _, _, c) -> c) (splitFunTy_maybe k), fmap snd (splitForAllTyCoVar_maybe k)] of
Nothing -> acc
Just ks -> go (acc + 1) ks
getDependenciesModuleNames :: ModuleGraph -> UnitId -> Dependencies -> [ModuleNameWithIsBoot]
getDependenciesModuleNames mg unitId deps =
mapMaybe nodeKeyToModuleName $ S.toList $ S.unions $ catMaybes
[ Map.lookup k tdeps
| (_, m) <- S.toList $ dep_direct_mods deps
, let k = NodeKey_Module $ ModNodeKeyWithUid m unitId
]
where
tdeps = mgTransDeps mg
nodeKeyToModuleName (NodeKey_Module (ModNodeKeyWithUid m _)) = Just m
nodeKeyToModuleName _ = Nothing
renderWithStyle :: DynFlags -> SDoc -> PprStyle -> String
renderWithStyle dynflags sdoc style = Ghc.renderWithContext (Ghc.initSDocContext dynflags style) sdoc
-- This function is gone in GHC 9.
dataConSig :: DataCon -> ([TyCoVar], ThetaType, [Type], Type)
dataConSig dc
= (dataConUnivAndExTyCoVars dc, dataConTheta dc, map irrelevantMult $ dataConOrigArgTys dc, dataConOrigResTy dc)
-- | The collection of dependencies and usages modules which are relevant for liquidHaskell
relevantModules :: ModuleGraph -> ModGuts -> S.Set Module
relevantModules mg modGuts = used `S.union` dependencies
where
dependencies :: S.Set Module
dependencies = S.fromList $ map (toModule . gwib_mod)
. filter ((NotBoot ==) . gwib_isBoot)
. getDependenciesModuleNames mg thisUnitId $ deps
deps :: Dependencies
deps = mg_deps modGuts
thisModule :: Module
thisModule = mg_module modGuts
thisUnitId = moduleUnitId thisModule
toModule :: ModuleName -> Module
toModule = unStableModule . mkStableModule thisUnitId
used :: S.Set Module
used = S.fromList $ foldl' collectUsage mempty . mg_usages $ modGuts
where
collectUsage :: [Module] -> Usage -> [Module]
collectUsage acc = \case
UsagePackageModule { usg_mod = modl } -> modl : acc
UsageHomeModule { usg_mod_name = modName } -> toModule modName : acc
UsageMergedRequirement { usg_mod = modl } -> modl : acc
_ -> acc
--
-- Parsing, typechecking and desugaring a module
--
parseModuleIO :: HscEnv -> ModSummary -> IO ParsedModule
parseModuleIO hscEnv ms = do
let hsc_env_tmp = hscEnv { hsc_dflags = ms_hspp_opts ms }
hpm <- hscParse hsc_env_tmp ms
return (ParsedModule ms (hpm_module hpm) (hpm_src_files hpm))
-- | Our own simplified version of 'TypecheckedModule'.
data TypecheckedModuleLH = TypecheckedModuleLH {
tmlh_parsed_module :: ParsedModule
, tmlh_renamed_source :: Maybe RenamedSource
, tmlh_mod_summary :: ModSummary
, tmlh_gbl_env :: TcGblEnv
}
typecheckModuleIO :: HscEnv -> ParsedModule -> IO TypecheckedModuleLH
typecheckModuleIO hscEnv pmod = do
-- Suppress all the warnings, so that they won't be printed (which would result in them being
-- printed twice, one by GHC and once here).
let ms = pm_mod_summary pmod
let dynFlags' = ms_hspp_opts ms
let hsc_env_tmp = hscEnv { hsc_dflags = dynFlags' { warningFlags = EnumSet.empty } }
(tc_gbl_env, rn_info)
<- hscTypecheckRename hsc_env_tmp ms $
HsParsedModule { hpm_module = parsedSource pmod,
hpm_src_files = pm_extra_src_files pmod }
return TypecheckedModuleLH {
tmlh_parsed_module = pmod
, tmlh_renamed_source = rn_info
, tmlh_mod_summary = ms
, tmlh_gbl_env = tc_gbl_env
}
-- | Desugar a typechecked module.
desugarModuleIO :: HscEnv -> ModSummary -> TypecheckedModuleLH -> IO ModGuts
desugarModuleIO hscEnv originalModSum typechecked = do
-- See [NOTE:ghc810] on why we override the dynFlags here before calling 'desugarModule'.
let modSum = originalModSum { ms_hspp_opts = hsc_dflags hscEnv }
let parsedMod' = (tmlh_parsed_module typechecked) { pm_mod_summary = modSum }
let typechecked' = typechecked { tmlh_parsed_module = parsedMod' }
let hsc_env_tmp = hscEnv { hsc_dflags = ms_hspp_opts (tmlh_mod_summary typechecked') }
hscDesugar hsc_env_tmp (tmlh_mod_summary typechecked') (tmlh_gbl_env typechecked')
-- | Abstraction of 'EpaComment'.
data ApiComment
= ApiLineComment String
| ApiBlockComment String
deriving (Eq, Show)
-- | Extract top-level comments from a module.
apiComments :: ParsedModule -> [Ghc.Located ApiComment]
apiComments pm = apiCommentsParsedSource (pm_parsed_source pm)
apiCommentsParsedSource :: Located (HsModule GhcPs) -> [Ghc.Located ApiComment]
apiCommentsParsedSource ps =
let hs = unLoc ps
go :: forall a. Data a => a -> [LEpaComment]
go = gmapQr (++) [] go `extQ` (id @[LEpaComment])
in Data.List.sortOn (spanToLineColumn . getLoc) $
mapMaybe (tokComment . toRealSrc) $ go hs
where
tokComment (L sp (EpaComment (EpaLineComment s) _)) = Just (L sp (ApiLineComment s))
tokComment (L sp (EpaComment (EpaBlockComment s) _)) = Just (L sp (ApiBlockComment s))
tokComment _ = Nothing
-- TODO: take into account anchor_op, which only matters if the source was
-- pre-processed by an exact-print-aware tool.
toRealSrc (L a e) = L (RealSrcSpan (anchor a) strictNothing) e
spanToLineColumn =
fmap (\s -> (srcSpanStartLine s, srcSpanStartCol s)) . srcSpanToRealSrcSpan
lookupModSummary :: HscEnv -> ModuleName -> Maybe ModSummary
lookupModSummary hscEnv mdl = do
let mg = hsc_mod_graph hscEnv
mods_by_name = [ ms | ms <- mgModSummaries mg
, ms_mod_name ms == mdl
, NotBoot == isBootSummary ms ]
case mods_by_name of
[ms] -> Just ms
_ -> Nothing
-- | Our own simplified version of 'ModuleInfo' to overcome the fact we cannot construct the \"original\"
-- one as the constructor is not exported, and 'getHomeModuleInfo' and 'getPackageModuleInfo' are not
-- exported either, so we had to backport them as well.
newtype ModuleInfoLH = ModuleInfoLH { minflh_type_env :: UniqFM Name TyThing }
modInfoLookupNameIO :: HscEnv
-> ModuleInfoLH
-> Name
-> IO (Maybe TyThing)
modInfoLookupNameIO hscEnv minf name =
case lookupTypeEnv (minflh_type_env minf) name of
Just tyThing -> return (Just tyThing)
Nothing -> lookupType hscEnv name
moduleInfoTc :: HscEnv -> TcGblEnv -> IO ModuleInfoLH
moduleInfoTc hscEnv tcGblEnv = do
details <- md_types <$> liftIO (makeSimpleDetails (hsc_logger hscEnv) tcGblEnv)
pure ModuleInfoLH { minflh_type_env = details }
-- | Tells if a case alternative calls to patError
isPatErrorAlt :: CoreAlt -> Bool
isPatErrorAlt (Alt _ _ exprCoreBndr) = hasPatErrorCall exprCoreBndr
where
hasPatErrorCall :: CoreExpr -> Bool
-- auto generated undefined case: (\_ -> (patError @levity @type "error message")) void
-- Type arguments are erased before calling isUndefined
hasPatErrorCall (App (Var x) _) = x == pAT_ERROR_ID
-- another auto generated undefined case:
-- let lqanf_... = patError "error message") in case lqanf_... of {}
hasPatErrorCall (Let (NonRec x e) (Case (Var v) _ _ []))
| x == v = hasPatErrorCall e
hasPatErrorCall (Let _ e) = hasPatErrorCall e
-- otherwise
hasPatErrorCall _ = False
qualifiedNameFS :: Name -> FastString
qualifiedNameFS n = concatFS [modFS, occFS, uniqFS]
where
modFS = case nameModule_maybe n of
Nothing -> fsLit ""
Just m -> concatFS [moduleNameFS (moduleName m), fsLit "."]
occFS = occNameFS (getOccName n)
uniqFS
| isSystemName n
= concatFS [fsLit "_", fsLit (showPprQualified (getUnique n))]
| otherwise
= fsLit ""
-- Variants of Outputable functions which now require DynFlags!
showPprQualified :: Outputable a => a -> String
showPprQualified = showSDocQualified . ppr
showSDocQualified :: Ghc.SDoc -> String
showSDocQualified = Ghc.renderWithContext ctx
where
style = Ghc.mkUserStyle myQualify Ghc.AllTheWay
ctx = Ghc.defaultSDocContext { sdocStyle = style }
myQualify :: Ghc.NamePprCtx
myQualify = Ghc.neverQualify { Ghc.queryQualifyName = Ghc.alwaysQualifyNames }
-- { Ghc.queryQualifyName = \_ _ -> Ghc.NameNotInScope1 }
strictNothing :: GHC.Data.Strict.Maybe a
strictNothing = GHC.Data.Strict.Nothing