From cf2c56c7061b7ed40fdd3b40a352ddb9c9b7371f Mon Sep 17 00:00:00 2001 From: alexwl Date: Tue, 2 Oct 2018 13:17:04 +0300 Subject: Initial commit --- src/HaskellCodeExplorer/GhcUtils.hs | 1122 +++++++++++++++++++++++++++++++++++ 1 file changed, 1122 insertions(+) create mode 100644 src/HaskellCodeExplorer/GhcUtils.hs (limited to 'src/HaskellCodeExplorer/GhcUtils.hs') diff --git a/src/HaskellCodeExplorer/GhcUtils.hs b/src/HaskellCodeExplorer/GhcUtils.hs new file mode 100644 index 0000000..714e429 --- /dev/null +++ b/src/HaskellCodeExplorer/GhcUtils.hs @@ -0,0 +1,1122 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE Rank2Types #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE RecordWildCards #-} + +module HaskellCodeExplorer.GhcUtils + ( -- * Pretty-printing + toText + , instanceToText + , instanceDeclToText + , nameToText + , tyClDeclPrefix + , demangleOccName + , stringBufferToByteString + , nameSort + , occNameNameSpace + , identifierKey + , nameKey + , mbIdDetails + -- * Syntax manipulation + , hsGroupVals + , hsPatSynDetails + , ieLocNames + -- * Lookups + , lookupIdInTypeEnv + , lookupNameModuleAndPackage + -- * Location info + , isHsBoot + , moduleLocationInfo + , nameLocationInfo + , occNameLocationInfo + , nameDocumentation + , srcSpanToLineAndColNumbers + -- * Type-related functions + , tyThingToId + , tidyIdentifierType + , patSynId + , applyWrapper + , wrapperTypes + , tyVarsOfType + , tyConsOfType + , updateOccNames + , mkType + -- * Documentation processing + , collectDocs + , ungroup + , mkDecls + , getMainDeclBinder + , classDeclDocs + , sigNameNoLoc + , clsInstDeclSrcSpan + , hsDocsToDocH + , subordinateNamesWithDocs + ) where +import Bag (bagToList) +import ConLike (ConLike(..)) +import qualified Data.ByteString as BS +import Data.Hashable (Hashable,hash) +import qualified Data.ByteString.Internal as BSI +import Data.Char (isAlpha, isAlphaNum, isAscii, ord) +import Data.Either (either) +import Data.Generics (Data) +import Data.Generics.SYB (everything, everywhere, mkQ, mkT) +import qualified Data.Generics.Uniplate.Data() +import qualified Data.HashMap.Strict as HM +import qualified Data.List as L +import Data.Maybe (fromMaybe, isJust, mapMaybe) +import Data.Ord (comparing) +import qualified Data.Text as T +import DataCon (dataConWorkId, flSelector) +import Documentation.Haddock.Parser (overIdentifier, parseParas) +import Documentation.Haddock.Types (DocH(..), Header(..), _doc) +import DynFlags () +import FastString (mkFastString, unpackFS) +import GHC + ( DynFlags + , HsDocString(..) + , InstDecl(..) + , ModuleName + , Name + , SrcSpan(..) + , RealSrcSpan(..) + , ClsInstDecl(..) + , TyClDecl(..) + , HsDataDefn(..) + , NewOrData(..) + , Id + , HsGroup(..) + , HsBindLR(..) + , HsValBindsLR(..) + , HsPatSynDetails(..) + , Located + , IE(..) + , TyThing(..) + , LHsDecl + , HsDecl(..) + , DocDecl(..) + , ConDecl(..) + , PostRn + , HsConDetails(..) + , ConDeclField(..) + , DataFamInstDecl(..) + , LSig + , Sig(..) + , ForeignDecl(..) + , FixitySig(..) + , tcdName + , collectHsBindBinders + , getLoc + , hsSigType + , getConNames + , getConDetails + , selectorFieldOcc +#if MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) + , tyClGroupTyClDecls + , LIEWrappedName + , hsGroupInstDecls + , ieLWrappedName +#else + , tyClGroupConcat +#endif + , tyConKind + , nameSrcSpan + , srcSpanFile + , srcSpanStartLine + , srcSpanEndLine + , srcSpanStartCol + , srcSpanEndCol + , isExternalName + , moduleNameString + , recordPatSynSelectorId + , recordPatSynPatVar + , isGoodSrcSpan + , isLocalId + , isDataFamilyDecl + , tyFamInstDeclName + , idType + , hsib_body + , tfe_pats + , tfid_eqn + ) +import qualified HaskellCodeExplorer.Types as HCE +import HscTypes (TypeEnv, lookupTypeEnv) +import IdInfo (IdDetails(..)) +import InstEnv (ClsInst(..)) +import Lexer (ParseResult(POk), mkPState, unP) +import Module (Module(..)) +import Name + ( isDataConNameSpace + , isDerivedOccName + , isInternalName + , isSystemName + , isTvNameSpace + , isTyConName + , isVarNameSpace + , isWiredInName + , mkInternalName + , mkOccName + , nameModule_maybe + , nameOccName + , nameUnique + , occNameFS + , occNameSpace + , occNameString + , wiredInNameTyThing_maybe + ) +import OccName (OccName) +import Outputable (Outputable, ppr, showPpr, showSDoc) +import PackageConfig (packageVersion) +import Packages + ( LookupResult(..) + , lookupModuleWithSuggestions + , lookupPackage + , packageNameString + ) +import Pair (pSnd) +import Parser (parseIdentifier) +import PatSyn (PatSyn, patSynMatcher, patSynSig) +import Prelude hiding (id, span) +import RdrName (GlobalRdrEnv, RdrName(..), gre_name, lookupGRE_RdrName) +import RnEnv (dataTcOccs) +import SrcLoc (GenLocated(..), mkRealSrcLoc, unLoc) +import StringBuffer (StringBuffer(..), stringToStringBuffer) +import System.FilePath (normalise) +import TcEvidence (HsWrapper(..), tcCoercionKind) +import TcType (evVarPred) +import TyCoRep (Type(..), +#if MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) + ArgFlag(..) +#else + VisibilityFlag(..) +#endif + ) +import TyCon (tyConName) +import Type + ( coreView + , expandTypeSynonyms + , mkForAllTy + , mkFunTy + , mkFunTys + , mkInvForAllTys +#if !MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) + , mkNamedBinder +#endif + , piResultTy + , pprSigmaType + , splitFunTy_maybe + , tidyOpenType + ) +#if MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) +import ToIface +import IfaceType +#endif +import TysWiredIn (unitTy) +import UniqSet (emptyUniqSet, unionUniqSets, +#if MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) + nonDetEltsUniqSet +#else + uniqSetToList +#endif + ) +import Unique (getKey) +import Var + ( idDetails + , isId + , mkTyVar + , setVarType + , varName + , varType + , varUnique + ) +import VarEnv (TidyEnv) +import VarSet (VarSet, emptyVarSet, unionVarSet, unitVarSet +#if MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) +#else + ,varSetElems +#endif + ) + +-------------------------------------------------------------------------------- +-- Pretty-printing +-------------------------------------------------------------------------------- + +toText :: (Outputable a) => DynFlags -> a -> T.Text +toText flags = T.pack . showSDoc flags . ppr + +instanceToText :: DynFlags -> ClsInst -> T.Text +instanceToText flags ClsInst {..} = + T.append "instance " $ T.pack . showSDoc flags $ pprSigmaType (idType is_dfun) + +instanceDeclToText :: DynFlags -> InstDecl Name -> T.Text +instanceDeclToText flags decl = + case decl of + ClsInstD ClsInstDecl {..} -> T.append "instance " (toText flags cid_poly_ty) + DataFamInstD di -> + let args = + T.intercalate " " . map (toText flags) . hsib_body $ dfid_pats di + in T.concat + ["data instance ", toText flags (unLoc $ dfid_tycon di), " ", args] + TyFamInstD ti -> + let args = + T.intercalate " " . + map (toText flags) . hsib_body . tfe_pats . unLoc . tfid_eqn $ + ti + in T.concat + ["type instance ", toText flags $ tyFamInstDeclName ti, " ", args] + +nameToText :: Name -> T.Text +nameToText = T.pack . unpackFS . occNameFS . nameOccName + +tyClDeclPrefix :: TyClDecl a -> T.Text +tyClDeclPrefix tyClDecl = + let isNewTy :: TyClDecl a -> Bool + isNewTy DataDecl {tcdDataDefn = HsDataDefn {dd_ND = NewType}} = True + isNewTy _ = False + in case tyClDecl of + FamDecl _ + | isDataFamilyDecl tyClDecl -> "data family " + | otherwise -> "type family " + SynDecl {} -> "type " + DataDecl {} + | isNewTy tyClDecl -> "newtype " + | otherwise -> "data " + ClassDecl {} -> "class " + +demangleOccName :: Name -> T.Text +demangleOccName name + | isDerivedOccName (nameOccName name) = + let removePrefix :: T.Text -> T.Text + removePrefix occName + | T.isPrefixOf "$sel:" occName = + fst $ T.breakOn ":" (T.drop 5 occName) + | T.isPrefixOf "$W" occName = T.drop 2 occName + | T.isPrefixOf "$w" occName = T.drop 2 occName + | T.isPrefixOf "$m" occName = T.drop 2 occName + | T.isPrefixOf "$b" occName = T.drop 2 occName + | T.isPrefixOf "$dm" occName = T.drop 3 occName + | T.isPrefixOf "$c" occName = T.drop 2 occName + | T.isPrefixOf "$d" occName = T.drop 2 occName + | T.isPrefixOf "$i" occName = T.drop 2 occName + | T.isPrefixOf "$s" occName = T.drop 2 occName + | T.isPrefixOf "$f" occName = T.drop 2 occName + | T.isPrefixOf "$r" occName = T.drop 2 occName + | T.isPrefixOf "C:" occName = T.drop 2 occName + | T.isPrefixOf "N:" occName = T.drop 2 occName + | T.isPrefixOf "D:" occName = T.drop 2 occName + | T.isPrefixOf "$co" occName = T.drop 3 occName + | otherwise = occName + in removePrefix $ nameToText name + | otherwise = nameToText name + +stringBufferToByteString :: StringBuffer -> BS.ByteString +stringBufferToByteString (StringBuffer buf len cur) = + BSI.fromForeignPtr buf cur len + +nameSort :: Name -> HCE.NameSort +nameSort n = + if isExternalName n + then HCE.External + else HCE.Internal + +occNameNameSpace :: OccName -> HCE.NameSpace +occNameNameSpace n + | isVarNameSpace (occNameSpace n) = HCE.VarName + | isDataConNameSpace (occNameSpace n) = HCE.DataName + | isTvNameSpace (occNameSpace n) = HCE.TvName + | otherwise = HCE.TcClsName + +-- Two 'Id''s may have different types even though they have the same 'Unique'. +identifierKey :: DynFlags -> Id -> T.Text +identifierKey flags id + | isLocalId id = + T.concat + [ T.pack . show . getKey . varUnique $ id + , "_" + , T.pack . show . hash . showSDoc flags . ppr . varType $ id + ] +identifierKey _ id = T.pack . show . getKey . varUnique $ id + +nameKey :: Name -> T.Text +nameKey = T.pack . show . getKey . nameUnique + +mbIdDetails :: Id -> Maybe HCE.IdDetails +mbIdDetails v + | isId v = + case idDetails v of + VanillaId -> Just HCE.VanillaId + RecSelId {sel_naughty = False} -> Just HCE.RecSelId + RecSelId {sel_naughty = True} -> Just HCE.RecSelIdNaughty + DataConWorkId _ -> Just HCE.DataConWorkId + DataConWrapId _ -> Just HCE.DataConWrapId + ClassOpId _ -> Just HCE.ClassOpId + PrimOpId _ -> Just HCE.PrimOpId + FCallId _ -> Just HCE.FCallId + TickBoxOpId _ -> Just HCE.TickBoxOpId + DFunId _ -> Just HCE.DFunId + CoVarId -> Just HCE.CoVarId +#if MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) + JoinId _ -> Just HCE.JoinId +#endif +mbIdDetails _ = Nothing + +-------------------------------------------------------------------------------- +-- Syntax transformation +-------------------------------------------------------------------------------- + +hsGroupVals :: HsGroup Name -> [GenLocated SrcSpan (HsBindLR Name Name)] +hsGroupVals hsGroup = + filter (isGoodSrcSpan . getLoc) $ + case hs_valds hsGroup of + ValBindsOut binds _ -> concatMap (bagToList . snd) binds + _ -> [] + +hsPatSynDetails :: HsPatSynDetails a -> [a] +hsPatSynDetails patDetails = + case patDetails of + InfixPatSyn name1 name2 -> [name1, name2] + PrefixPatSyn name -> name + RecordPatSyn fields -> + concatMap + (\field -> [recordPatSynSelectorId field, recordPatSynPatVar field]) + fields + +#if MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) +unwrapName :: LIEWrappedName Name -> Located Name +unwrapName = ieLWrappedName +#else +unwrapName :: Located Name -> Located Name +unwrapName n = n +#endif + +ieLocNames :: IE Name -> [Located Name] +ieLocNames (IEVar n) = [unwrapName n] +ieLocNames (IEThingAbs n) = [unwrapName n] +ieLocNames (IEThingAll n) = [unwrapName n] +ieLocNames (IEThingWith n _ ns labels) = + unwrapName n : (map unwrapName ns ++ map (fmap flSelector) labels) +ieLocNames (IEModuleContents (L _ _)) = [] +ieLocNames (IEGroup _ _) = [] +ieLocNames (IEDoc _) = [] +ieLocNames (IEDocNamed _) = [] + +-------------------------------------------------------------------------------- +-- Lookups +-------------------------------------------------------------------------------- + +lookupIdInTypeEnv :: TypeEnv -> Name -> Maybe Id +lookupIdInTypeEnv typeEnv name = do + let mbTyThing + | isInternalName name = Nothing + | isSystemName name = Nothing + | isWiredInName name = wiredInNameTyThing_maybe name + | isExternalName name = lookupTypeEnv typeEnv name + | otherwise = Nothing + case mbTyThing of + Just tyThing -> tyThingToId tyThing + _ -> Nothing + +lookupNameModuleAndPackage :: + DynFlags + -> HCE.PackageId + -> Name + -> Either T.Text (HCE.HaskellModuleName, HCE.PackageId) +lookupNameModuleAndPackage flags currentPackageId name = + case nameModule_maybe name of + Just Module {..} -> + case lookupPackage flags moduleUnitId of + Just packageConfig -> + let packageId = + if (T.pack . packageNameString $ packageConfig) == + HCE.name (currentPackageId :: HCE.PackageId) + then currentPackageId + else HCE.PackageId + (T.pack $ packageNameString packageConfig) + (PackageConfig.packageVersion packageConfig) + in Right + ( HCE.HaskellModuleName . T.pack . moduleNameString $ moduleName + , packageId) + Nothing -> + Right + ( HCE.HaskellModuleName . T.pack . moduleNameString $ moduleName + , currentPackageId) + Nothing -> + Left $ T.concat ["nameModule_maybe ", nameToText name, " is Nothing"] + +-------------------------------------------------------------------------------- +-- Location info +-------------------------------------------------------------------------------- + +isHsBoot :: HCE.HaskellModulePath -> Bool +isHsBoot = T.isSuffixOf "-boot" . HCE.getHaskellModulePath + +moduleLocationInfo :: + DynFlags + -> HM.HashMap HCE.HaskellModuleName (HM.HashMap HCE.ComponentId HCE.HaskellModulePath) + -> HCE.PackageId + -> HCE.ComponentId + -> ModuleName + -> HCE.LocationInfo +moduleLocationInfo flags moduleNameMap currentPackageId compId moduleName = + let moduleNameText = T.pack . moduleNameString $ moduleName + currentPackageLocation = + HCE.ApproximateLocation + currentPackageId + (HCE.HaskellModuleName . T.pack . moduleNameString $ moduleName) + HCE.Mod + moduleNameText + Nothing + compId + in case HM.lookup (HCE.HaskellModuleName moduleNameText) moduleNameMap of + Just modulePathMap + | Just modulePath <- HM.lookup compId modulePathMap -> + HCE.ExactLocation + currentPackageId + modulePath + (HCE.HaskellModuleName moduleNameText) + 1 + 1 + 1 + 1 + _ -> + case lookupModuleWithSuggestions flags moduleName Nothing of + LookupFound Module {moduleUnitId = unitId} _ -> + case lookupPackage flags unitId of + Just packInfo -> + let packageId = + HCE.PackageId + (T.pack $ packageNameString packInfo) + (PackageConfig.packageVersion packInfo) + in HCE.ApproximateLocation + packageId + (HCE.HaskellModuleName . T.pack . moduleNameString $ + moduleName) + HCE.Mod + moduleNameText + Nothing + (if packageId == currentPackageId + then compId + else HCE.ComponentId "lib") + Nothing -> currentPackageLocation + _ -> currentPackageLocation + +isDefinedInCurrentModule :: + HCE.SourceCodeTransformation -> HCE.HaskellFilePath -> Bool +isDefinedInCurrentModule transformation file = + let includedFiles = HM.keys $ HCE.fileIndex transformation + modPath = + HCE.getHaskellModulePath $ + HCE.filePath (transformation :: HCE.SourceCodeTransformation) + in HCE.getHaskellFilePath file == modPath || (file `elem` includedFiles) + +nameLocationInfo :: + DynFlags + -> HCE.PackageId + -> HCE.ComponentId + -> HCE.SourceCodeTransformation + -> HM.HashMap HCE.HaskellFilePath HCE.HaskellModulePath + -> HM.HashMap HCE.HaskellModulePath HCE.DefinitionSiteMap + -> Maybe T.Text -- ^ Instance head (when name is a dictionary function) + -> Maybe SrcSpan -- ^ Only for wired-in names + -> Name + -> HCE.LocationInfo +nameLocationInfo flags currentPackageId compId transformation fileMap defSiteMap mbInstanceHead mbSrcSpan name + | Just srcSpan <- realSrcSpan name mbSrcSpan = + let filePath = + HCE.HaskellFilePath . T.pack . normalise . unpackFS . srcSpanFile $ + srcSpan + approximateLocation = + mkApproximateLocation + flags + currentPackageId + compId + mbInstanceHead + name + in if isDefinedInCurrentModule transformation filePath + then let eitherStart = + HCE.fromOriginalLineNumber + transformation + (filePath, srcSpanStartLine srcSpan) + eitherEnd = + HCE.fromOriginalLineNumber + transformation + (filePath, srcSpanEndLine srcSpan) + in case (,) eitherStart eitherEnd of + (Right startLine,Right endLine) -> + let modulePath = HCE.filePath (transformation :: HCE.SourceCodeTransformation) + moduleName = + either + (const $ HCE.HaskellModuleName "") + fst + (lookupNameModuleAndPackage flags currentPackageId name) + in HCE.ExactLocation + { packageId = currentPackageId + , modulePath = modulePath + , moduleName = moduleName + , startLine = startLine + , endLine = endLine + , startColumn = srcSpanStartCol srcSpan + , endColumn = srcSpanEndCol srcSpan + } + _ -> approximateLocation + else case HM.lookup filePath fileMap of + Just haskellModulePath -> + case HM.lookup haskellModulePath defSiteMap of + Just defSites -> + let key = fromMaybe (nameToText name) mbInstanceHead + in lookupEntityLocation + defSites + (mkLocatableEntity name mbInstanceHead) + key + Nothing -> approximateLocation + Nothing -> approximateLocation + where + realSrcSpan :: Name -> Maybe SrcSpan -> Maybe RealSrcSpan + realSrcSpan name mbSrcSpan = + case nameSrcSpan name of + RealSrcSpan span -> Just span + _ + | isWiredInName name -> + case mbSrcSpan of + Just span -> + case span of + RealSrcSpan s -> Just s + _ -> Nothing + _ -> Nothing + _ -> Nothing +nameLocationInfo flags currentPackageId compId _transformation _fileMap _defSiteMap mbInstanceHead _mbSrcSpan name = + mkApproximateLocation flags currentPackageId compId mbInstanceHead name + +mkApproximateLocation :: + DynFlags + -> HCE.PackageId + -> HCE.ComponentId + -> Maybe T.Text + -> Name + -> HCE.LocationInfo +mkApproximateLocation flags currentPackageId compId mbInstanceHead name = + let haddockAnchor = + Just . T.pack . makeAnchorId . T.unpack . nameToText $ name + in case lookupNameModuleAndPackage flags currentPackageId name of + Right (moduleName, packageId) -> + HCE.ApproximateLocation + { moduleName = moduleName + , packageId = packageId + , componentId = + if packageId == currentPackageId + then compId + else HCE.ComponentId "lib" + , entity = mkLocatableEntity name mbInstanceHead + , haddockAnchorId = haddockAnchor + , name = fromMaybe (nameToText name) mbInstanceHead + } + Left errorMessage -> HCE.UnknownLocation errorMessage + +mkLocatableEntity :: Name -> Maybe a -> HCE.LocatableEntity +mkLocatableEntity name mbInstanceHead + | isJust mbInstanceHead = HCE.Inst + | otherwise = + case occNameNameSpace . nameOccName $ name of + HCE.VarName -> HCE.Val + HCE.DataName -> HCE.Val + _ -> HCE.Typ + +occNameLocationInfo :: + DynFlags + -> HCE.PackageId + -> HCE.ComponentId + -> (ModuleName, OccName) + -> HCE.LocationInfo +occNameLocationInfo flags packageId componentId (modName, occName) = + HCE.ApproximateLocation + { packageId = packageId + , moduleName = HCE.HaskellModuleName $ toText flags modName + , entity = + case occNameNameSpace occName of + HCE.VarName -> HCE.Val + HCE.DataName -> HCE.Val + _ -> HCE.Typ + , name = toText flags occName + , haddockAnchorId = + Just . T.pack . makeAnchorId . T.unpack $ toText flags occName + , componentId = componentId + } + +lookupEntityLocation :: + HCE.DefinitionSiteMap -> HCE.LocatableEntity -> T.Text -> HCE.LocationInfo +lookupEntityLocation defSiteMap locatableEntity text = + let errorMessage = + T.concat + [ "Cannot find location of " + , T.pack . show $ locatableEntity + , " " + , text + ] + defSiteLocation = HCE.location :: HCE.DefinitionSite -> HCE.LocationInfo + lookupLocation :: + (Eq a, Hashable a) + => (HCE.DefinitionSiteMap -> HM.HashMap a HCE.DefinitionSite) + -> (T.Text -> a) + -> HCE.LocationInfo + lookupLocation selector toKey = + maybe (HCE.UnknownLocation errorMessage) defSiteLocation $ + HM.lookup (toKey text) (selector defSiteMap) + in case locatableEntity of + HCE.Val -> lookupLocation HCE.values HCE.OccName + HCE.Typ -> lookupLocation HCE.types HCE.OccName + HCE.Inst -> lookupLocation HCE.instances (\t -> t) + HCE.Mod -> HCE.UnknownLocation errorMessage + +nameDocumentation :: + HCE.SourceCodeTransformation + -> HM.HashMap HCE.HaskellFilePath HCE.HaskellModulePath + -> HM.HashMap HCE.HaskellModulePath HCE.DefinitionSiteMap + -> HCE.DefinitionSiteMap + -> Name + -> Maybe T.Text +nameDocumentation transformation fileMap defSiteMap currentModuleDefSiteMap name + | isExternalName name || isWiredInName name + , Just file <- srcSpanToFilePath . nameSrcSpan $ name = + if isDefinedInCurrentModule transformation file + then lookupNameDocumentation name currentModuleDefSiteMap + else case HM.lookup file fileMap of + Just haskellModulePath -> + case HM.lookup haskellModulePath defSiteMap of + Just defSites -> lookupNameDocumentation name defSites + Nothing -> Nothing + Nothing -> Nothing +nameDocumentation _ _ _ _ _ = Nothing + +lookupNameDocumentation :: Name -> HCE.DefinitionSiteMap -> Maybe T.Text +lookupNameDocumentation name defSiteMap = + let key = HCE.OccName $ nameToText name + lookupDoc :: + (HCE.DefinitionSiteMap -> HM.HashMap HCE.OccName HCE.DefinitionSite) + -> Maybe T.Text + lookupDoc selector = + maybe Nothing HCE.documentation $ + HM.lookup key (selector (defSiteMap :: HCE.DefinitionSiteMap)) + in case occNameNameSpace . nameOccName $ name of + HCE.VarName -> lookupDoc HCE.values + HCE.DataName -> lookupDoc HCE.values + _ -> lookupDoc HCE.types + +srcSpanToFilePath :: SrcSpan -> Maybe HCE.HaskellFilePath +srcSpanToFilePath (RealSrcSpan s) = + Just . HCE.HaskellFilePath . T.pack . normalise . unpackFS . srcSpanFile $ s +srcSpanToFilePath (UnhelpfulSpan _) = Nothing + +srcSpanToLineAndColNumbers :: + HCE.SourceCodeTransformation + -> SrcSpan + -> Maybe (HCE.HaskellFilePath, (Int, Int), (Int, Int)) +srcSpanToLineAndColNumbers transformation (RealSrcSpan s) = + let filePath = + HCE.HaskellFilePath . T.pack . normalise . unpackFS . srcSpanFile $ s + eitherStart = + HCE.fromOriginalLineNumber transformation (filePath, srcSpanStartLine s) + eitherEnd = + HCE.fromOriginalLineNumber transformation (filePath, srcSpanEndLine s) + in case (,) eitherStart eitherEnd of + (Right startLine, Right endLine) -> + Just + ( filePath + , (startLine, srcSpanStartCol s) + , (endLine, srcSpanEndCol s)) + _ -> Nothing +srcSpanToLineAndColNumbers _ _ = Nothing + +-------------------------------------------------------------------------------- +-- Type-related functions +-------------------------------------------------------------------------------- + +tyThingToId :: TyThing -> Maybe Id +tyThingToId tyThing = + case tyThing of + AnId id -> Just id + ATyCon tc -> Just $ mkTyVar (tyConName tc) (tyConKind tc) + AConLike con -> + case con of + RealDataCon dataCon -> Just $ dataConWorkId dataCon + PatSynCon ps -> Just $ patSynId ps + ACoAxiom _ -> Nothing + +tidyIdentifierType :: TidyEnv -> Id -> (TidyEnv, Id) +tidyIdentifierType tidyEnv identifier = + let (tidyEnv', typ') = tidyOpenType tidyEnv (varType identifier) + in (tidyEnv', setVarType identifier typ') + +patSynId :: PatSyn -> Id +patSynId patSyn = + let (univTvs, reqTheta, exTvs, provTheta, argTys, resTy) = patSynSig patSyn + reqTheta' + | null reqTheta && not (null provTheta && null exTvs) = [unitTy] + | otherwise = reqTheta + -- required => provided => arg_1 -> ... -> arg_n -> res + patSynTy = + mkInvForAllTys univTvs $ + mkFunTys reqTheta' $ + mkInvForAllTys exTvs $ mkFunTys provTheta $ mkFunTys argTys resTy + in flip setVarType patSynTy . fst . patSynMatcher $ patSyn + +applyWrapper :: HsWrapper -> Type -> Type +applyWrapper wp ty + | Just ty' <- coreView ty = applyWrapper wp ty' +applyWrapper WpHole t = t +applyWrapper (WpCompose w1 w2) t = applyWrapper w1 . applyWrapper w2 $ t +#if MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) +applyWrapper (WpFun w1 w2 t1 _doc) t = mkFunTy t1 (applyWrapper w2 $ piResultTy t (applyWrapper w1 t1)) +#else +applyWrapper (WpFun w1 w2 t1) t = mkFunTy t1 (applyWrapper w2 $ piResultTy t (applyWrapper w1 t1)) +#endif +applyWrapper (WpCast coercion) _t = pSnd $ tcCoercionKind coercion +applyWrapper (WpEvLam v) t = mkFunTy (evVarPred v) t +applyWrapper (WpEvApp _ev) t = case splitFunTy_maybe t of + Just (_arg,res) -> res + Nothing -> t +#if MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) +applyWrapper (WpTyLam v) t = mkForAllTy v Required t +#else +applyWrapper (WpTyLam v) t = mkForAllTy (mkNamedBinder Invisible v) t +#endif +applyWrapper (WpTyApp t') t = piResultTy t t' +applyWrapper (WpLet _) t = t + +wrapperTypes :: HsWrapper -> [Type] +wrapperTypes WpHole = [] +wrapperTypes (WpCompose w1 w2) = wrapperTypes w2 ++ wrapperTypes w1 +#if MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) +wrapperTypes (WpFun w1 w2 _ _) = wrapperTypes w2 ++ wrapperTypes w1 +#else +wrapperTypes (WpFun w1 w2 _) = wrapperTypes w2 ++ wrapperTypes w1 +#endif +wrapperTypes (WpCast _) = [] +wrapperTypes (WpEvLam _) = [] +wrapperTypes (WpEvApp _) = [] +wrapperTypes (WpTyLam _) = [] +wrapperTypes (WpTyApp t) = [t] +wrapperTypes (WpLet _) = [] + +mkType :: DynFlags -> Type -> HCE.Type +mkType flags typ = + let typeExpanded = expandTypeSynonyms typ + typeComponents = toTypeComponents flags typ + typeComponentsExpanded = toTypeComponents flags typeExpanded + in HCE.Type + typeComponents + (if typeComponents /= typeComponentsExpanded + then Just typeComponentsExpanded + else Nothing) + +#if MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) +typeToText :: DynFlags -> Type -> T.Text +typeToText flags = T.pack . showSDoc flags . pprIfaceType . toIfaceType +#else +typeToText :: DynFlags -> Type -> T.Text +typeToText = toText +#endif + +toTypeComponents :: DynFlags -> Type -> [HCE.TypeComponent] +toTypeComponents flags typ = + let signature = + typeToText flags $ + updateOccNames (\_unique occName -> ";" ++ drop 2 occName ++ ";") typ + -- Signature with OccNames and uniques + signatureWithUniques = + typeToText flags $ + updateOccNames + (\unique occName -> ";," ++ occName ++ "," ++ unique ++ ";") + typ + -- Dirty but simple way to extract a list of TypeComponent from a type signature. + -- Assumptions : + -- 1. Character ';' cannot appear anywhere in a type signature + -- 2. Character ',' cannot appear in an 'OccName' + -- 3. length (T.splitOn ";" signature) == length (T.splitOn ";" signatureWithUniques) + components = + L.zip (T.splitOn ";" signature) (T.splitOn ";" signatureWithUniques) + in mapMaybe + (\(text1, text2) -> + if T.isPrefixOf "," text2 + then case T.splitOn "," text2 of + ["", name, id] -> + Just HCE.TyCon {name = name, internalId = HCE.InternalId id} + _ -> Just $ HCE.Text text1 + else if T.null text1 + then Nothing + else Just $ HCE.Text text1) + components + +-- | Replaces 'OccName' of each type variable and type constructor in a type. +updateOccNames :: (String -> String -> String) -> Type -> Type +updateOccNames update = everywhere (mkT updateType) + where + updateType :: Type -> Type + updateType (TyVarTy var) = TyVarTy var {varName = updateName (varName var)} + updateType (TyConApp con args) = + TyConApp (con {tyConName = updateName (tyConName con)}) args + updateType other = other + updateName :: Name -> Name + updateName oldName = + let oldOccName = nameOccName oldName + unique = T.unpack $ nameKey oldName + newOccName = + mkOccName + (occNameSpace oldOccName) + (update unique (occNameString oldOccName)) + in mkInternalName (nameUnique oldName) newOccName (nameSrcSpan oldName) + +-- | This function doesn't look through type synonyms +tyConsOfType :: Type -> [Id] +tyConsOfType = +#if MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) + nonDetEltsUniqSet . everything unionUniqSets (emptyVarSet `mkQ` tyCon) +#else + uniqSetToList . everything unionUniqSets (emptyVarSet `mkQ` tyCon) +#endif + where + tyCon :: Type -> VarSet + tyCon (TyConApp tc _) = unitVarSet $ mkTyVar (tyConName tc) (tyConKind tc) + tyCon _ = emptyUniqSet + +tyVarsOfType :: (Data a) => a -> [Id] +#if MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) +tyVarsOfType = nonDetEltsUniqSet . everything unionVarSet (emptyVarSet `mkQ` tyVar) +#else +tyVarsOfType = varSetElems . everything unionVarSet (emptyVarSet `mkQ` tyVar) +#endif + where + tyVar :: Type -> VarSet + tyVar (TyVarTy ty) = unitVarSet ty + tyVar _ = emptyVarSet + +-------------------------------------------------------------------------------- +-- Documentation processing +-- Some functions are copied from haddock-api package +-------------------------------------------------------------------------------- + +collectDocs :: [LHsDecl a] -> [(LHsDecl a, [HsDocString])] +collectDocs = go Nothing [] + where + go Nothing _ [] = [] + go (Just prev) docs [] = finished prev docs [] + go prev docs (L _ (DocD (DocCommentNext str)):ds) + | Nothing <- prev = go Nothing (str : docs) ds + | Just decl <- prev = finished decl docs (go Nothing [str] ds) + go prev docs (L _ (DocD (DocCommentPrev str)):ds) = go prev (str : docs) ds + go Nothing docs (d:ds) = go (Just d) docs ds + go (Just prev) docs (d:ds) = finished prev docs (go (Just d) [] ds) + finished decl docs rest = (decl, reverse docs) : rest + +ungroup :: HsGroup Name -> [LHsDecl Name] +ungroup group_ = +#if MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) + mkDecls (tyClGroupTyClDecls . hs_tyclds) TyClD group_ ++ +#else + mkDecls (tyClGroupConcat . hs_tyclds) TyClD group_ ++ +#endif + mkDecls hs_derivds DerivD group_ ++ + mkDecls hs_defds DefD group_ ++ + mkDecls hs_fords ForD group_ ++ + mkDecls hs_docs DocD group_ ++ +#if MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) + mkDecls hsGroupInstDecls InstD group_ ++ +#else + mkDecls hs_instds InstD group_ ++ +#endif + mkDecls (typesigs . hs_valds) SigD group_ ++ + mkDecls (valbinds . hs_valds) ValD group_ + where + typesigs (ValBindsOut _ sigs) = filter isUserLSig sigs + typesigs _ = [] + valbinds (ValBindsOut binds _) = concatMap bagToList . snd . unzip $ binds + valbinds _ = [] + +mkDecls :: (a -> [Located b]) -> (b -> c) -> a -> [Located c] +mkDecls field con struct = [L loc (con decl) | L loc decl <- field struct] + +sortByLoc :: [Located a] -> [Located a] +sortByLoc = L.sortBy (comparing getLoc) + +classDeclDocs :: TyClDecl Name -> [(LHsDecl Name, [HsDocString])] +classDeclDocs class_ = collectDocs . sortByLoc $ decls + where + decls = docs ++ defs ++ sigs ++ ats + docs = mkDecls tcdDocs DocD class_ + defs = mkDecls (bagToList . tcdMeths) ValD class_ + sigs = mkDecls tcdSigs SigD class_ + ats = mkDecls tcdATs (TyClD . FamDecl) class_ + +conDeclDocs :: ConDecl Name -> [(Name, [HsDocString], SrcSpan)] +conDeclDocs conDecl = + map (\(L span n) -> (n, maybe [] ((: []) . unLoc) $ con_doc conDecl, span)) . + getConNames $ + conDecl + +selectorDocs :: ConDecl name -> [(PostRn name name, [HsDocString], SrcSpan)] +selectorDocs con = + case getConDetails con of + RecCon (L _ flds) -> + concatMap + (\(L _ (ConDeclField fieldOccs _ mbDoc)) -> + map + (\(L span f) -> + (selectorFieldOcc f, maybe [] ((: []) . unLoc) mbDoc, span)) + fieldOccs) + flds + _ -> [] + +subordinateNamesWithDocs :: + [GenLocated SrcSpan (HsDecl Name)] -> [(Name, [HsDocString], SrcSpan)] +subordinateNamesWithDocs = + concatMap + (\(L span tyClDecl) -> + case tyClDecl of + TyClD classDecl@ClassDecl {..} -> + concatMap + (\(L _ decl, docs) -> map (, docs, span) $ getMainDeclBinder decl) $ + classDeclDocs classDecl + TyClD DataDecl {..} -> + concatMap (\(L _ con) -> conDeclDocs con ++ selectorDocs con) $ + dd_cons tcdDataDefn + InstD (DataFamInstD DataFamInstDecl {..}) -> + concatMap (conDeclDocs . unLoc) . dd_cons $ dfid_defn + _ -> []) + +isUserLSig :: LSig name -> Bool +isUserLSig (L _ TypeSig {}) = True +isUserLSig (L _ ClassOpSig {}) = True +isUserLSig _ = False + +getMainDeclBinder :: HsDecl name -> [name] +getMainDeclBinder (TyClD d) = [tcdName d] +getMainDeclBinder (ValD d) = + case collectHsBindBinders d of + [] -> [] + (name:_) -> [name] +getMainDeclBinder (SigD d) = sigNameNoLoc d +getMainDeclBinder (ForD (ForeignImport name _ _ _)) = [unLoc name] +getMainDeclBinder (ForD ForeignExport {}) = [] +getMainDeclBinder _ = [] + +sigNameNoLoc :: Sig name -> [name] +sigNameNoLoc (TypeSig ns _) = map unLoc ns +sigNameNoLoc (ClassOpSig _ ns _) = map unLoc ns +#if MIN_VERSION_GLASGOW_HASKELL(8,2,2,0) +sigNameNoLoc (PatSynSig ns _) = map unLoc ns +#else +sigNameNoLoc (PatSynSig n _) = [unLoc n] +#endif +sigNameNoLoc (SpecSig n _ _) = [unLoc n] +sigNameNoLoc (InlineSig n _) = [unLoc n] +sigNameNoLoc (FixSig (FixitySig ns _)) = map unLoc ns +sigNameNoLoc _ = [] + +clsInstDeclSrcSpan :: ClsInstDecl name -> SrcSpan +clsInstDeclSrcSpan ClsInstDecl {cid_poly_ty = ty} = getLoc (hsSigType ty) + +hsDocsToDocH :: DynFlags -> GlobalRdrEnv -> [HsDocString] -> Doc Name +hsDocsToDocH flags rdrEnv = + rename flags rdrEnv . + overIdentifier (parseIdent flags) . + _doc . parseParas . concatMap (unpackFS . (\(HsDocString s) -> s)) + +parseIdent :: DynFlags -> String -> Maybe RdrName +parseIdent dflags str0 = + let buffer = stringToStringBuffer str0 + realSrcLc = mkRealSrcLoc (mkFastString "") 0 0 + pstate = mkPState dflags buffer realSrcLc + in case unP parseIdentifier pstate of + POk _ name -> Just (unLoc name) + _ -> Nothing + +type Doc id = DocH (ModuleName, OccName) id + +rename :: DynFlags -> GlobalRdrEnv -> Doc RdrName -> Doc Name +rename dflags gre = rn + where + rn d = case d of + DocAppend a b -> DocAppend (rn a) (rn b) + DocParagraph doc -> DocParagraph (rn doc) + DocIdentifier x -> do + -- Generate the choices for the possible kind of thing this + -- is. + let choices = dataTcOccs x + -- Try to look up all the names in the GlobalRdrEnv that match + -- the names. + let names = concatMap (\c -> map gre_name (lookupGRE_RdrName c gre)) choices + + case names of + -- We found no names in the env so we start guessing. + [] -> + case choices of + [] -> DocMonospaced (DocString (showPpr dflags x)) + -- There was nothing in the environment so we need to + -- pick some default from what's available to us. We + -- diverge here from the old way where we would default + -- to type constructors as we're much more likely to + -- actually want anchors to regular definitions than + -- type constructor names (such as in #253). So now we + -- only get type constructor links if they are actually + -- in scope. + a:_ -> outOfScope dflags a + + -- There is only one name in the environment that matches so + -- use it. + [a] -> DocIdentifier a + -- But when there are multiple names available, default to + -- type constructors: somewhat awfully GHC returns the + -- values in the list positionally. + a:b:_ | isTyConName a -> DocIdentifier a + | otherwise -> DocIdentifier b + + DocWarning doc -> DocWarning (rn doc) + DocEmphasis doc -> DocEmphasis (rn doc) + DocBold doc -> DocBold (rn doc) + DocMonospaced doc -> DocMonospaced (rn doc) + DocUnorderedList docs -> DocUnorderedList (map rn docs) + DocOrderedList docs -> DocOrderedList (map rn docs) + DocDefList list -> DocDefList [ (rn a, rn b) | (a, b) <- list ] + DocCodeBlock doc -> DocCodeBlock (rn doc) + DocIdentifierUnchecked x -> DocIdentifierUnchecked x + DocModule str -> DocModule str + DocHyperlink l -> DocHyperlink l + DocPic str -> DocPic str + DocMathInline str -> DocMathInline str + DocMathDisplay str -> DocMathDisplay str + DocAName str -> DocAName str + DocProperty p -> DocProperty p + DocExamples e -> DocExamples e + DocEmpty -> DocEmpty + DocString str -> DocString str + DocHeader (Header l t) -> DocHeader $ Header l (rn t) + +-- | Wrap an identifier that's out of scope (i.e. wasn't found in +-- 'GlobalReaderEnv' during 'rename') in an appropriate doc. Currently +-- we simply monospace the identifier in most cases except when the +-- identifier is qualified: if the identifier is qualified then we can +-- still try to guess and generate anchors accross modules but the +-- users shouldn't rely on this doing the right thing. See tickets +-- #253 and #375 on the confusion this causes depending on which +-- default we pick in 'rename'. +outOfScope :: DynFlags -> RdrName -> Doc a +outOfScope dflags x = + case x of + Unqual occ -> monospaced occ + Qual mdl occ -> DocIdentifierUnchecked (mdl, occ) + Orig _ occ -> monospaced occ + Exact name -> monospaced name -- Shouldn't happen since x is out of scope + where + monospaced a = DocMonospaced (DocString (showPpr dflags a)) + +makeAnchorId :: String -> String +makeAnchorId [] = [] +makeAnchorId (f:r) = escape isAlpha f ++ concatMap (escape isLegal) r + where + escape p c | p c = [c] + | otherwise = '-' : show (ord c) ++ "-" + isLegal ':' = True + isLegal '_' = True + isLegal '.' = True + isLegal c = isAscii c && isAlphaNum c -- cgit v1.2.3