From ac43c18319c411a314761564f86cf3b7fcec5603 Mon Sep 17 00:00:00 2001 From: Zubin Duggal Date: Sun, 29 Jul 2018 17:00:09 +0530 Subject: [PATCH 01/21] Use hiefiles to generate hyperlinked source --- haddock-api/haddock-api.cabal | 1 - haddock-api/resources/html/solarized.css | 42 ++++ haddock-api/src/Haddock.hs | 2 +- .../src/Haddock/Backends/Hyperlinker.hs | 25 +- .../src/Haddock/Backends/Hyperlinker/Ast.hs | 237 ------------------ .../Haddock/Backends/Hyperlinker/Renderer.hs | 115 ++++++--- .../src/Haddock/Backends/Hyperlinker/Types.hs | 23 -- haddock-api/src/Haddock/Interface/Create.hs | 29 ++- haddock-api/src/Haddock/Types.hs | 6 +- haddock.cabal | 1 - 10 files changed, 176 insertions(+), 305 deletions(-) delete mode 100644 haddock-api/src/Haddock/Backends/Hyperlinker/Ast.hs diff --git a/haddock-api/haddock-api.cabal b/haddock-api/haddock-api.cabal index 2a94c5f5f0..e69f2ae28e 100644 --- a/haddock-api/haddock-api.cabal +++ b/haddock-api/haddock-api.cabal @@ -97,7 +97,6 @@ library Haddock.Backends.HaddockDB Haddock.Backends.Hoogle Haddock.Backends.Hyperlinker - Haddock.Backends.Hyperlinker.Ast Haddock.Backends.Hyperlinker.Parser Haddock.Backends.Hyperlinker.Renderer Haddock.Backends.Hyperlinker.Types diff --git a/haddock-api/resources/html/solarized.css b/haddock-api/resources/html/solarized.css index e83dc5ec70..1bc2aa7844 100644 --- a/haddock-api/resources/html/solarized.css +++ b/haddock-api/resources/html/solarized.css @@ -53,3 +53,45 @@ a:link, a:visited { a:hover, a.hover-highlight { background-color: #eee8d5; } + +span.annot{ + position:relative; + color:#000; + text-decoration:none + } + +span.annot:hover{z-index:25; background-color:#ff0} + +span.annot span.annottext{ + display: none; + border-radius: 5px 5px; + + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.1); + -webkit-box-shadow: 5px 5px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 5px 5px rgba(0, 0, 0, 0.1); + + position: absolute; + left: 1em; top: 2em; + z-index: 99; + margin-left: 5; + background: #FFFFAA; + border: 2px solid #FFAD33; + padding: 0.8em 1em; +} + +span.annot:hover span.annottext{ + display:block; +} + +/* This bridges the gap so you can mouse into the tooltip without it disappearing */ +span.annot span.annottext:before{ + content: ""; + position: absolute; + left: -1em; top: -2em; + background: #FFFFFF00; + z-index:-1; + padding: 2em 2em; +} diff --git a/haddock-api/src/Haddock.hs b/haddock-api/src/Haddock.hs index 2bae60e762..f81e546c08 100644 --- a/haddock-api/src/Haddock.hs +++ b/haddock-api/src/Haddock.hs @@ -447,7 +447,7 @@ readInterfaceFiles name_cache_accessor pairs bypass_version_check = do withGhc' :: String -> [String] -> (DynFlags -> Ghc a) -> IO a withGhc' libDir flags ghcActs = runGhc (Just libDir) $ do dynflags <- getSessionDynFlags - dynflags' <- parseGhcFlags (gopt_set dynflags Opt_Haddock) { + dynflags' <- parseGhcFlags (foldl' gopt_set dynflags [Opt_IdeInfo, Opt_Haddock]) { hscTarget = HscNothing, ghcMode = CompManager, ghcLink = NoLink diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker.hs b/haddock-api/src/Haddock/Backends/Hyperlinker.hs index 8f0c4b674d..5ebfe1bc5c 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker.hs @@ -17,6 +17,12 @@ import Data.Maybe import System.Directory import System.FilePath +import HieTypes +import HieUtils (recoverFullType) +import Data.Map as M +import FastString +import HieDebug + -- | Generate hyperlinked source for given interfaces. -- @@ -44,9 +50,22 @@ ppHyperlinkedSource outdir libdir mstyle pretty srcs ifaces = do ppHyperlinkedModuleSource :: FilePath -> Bool -> SrcMap -> Interface -> IO () ppHyperlinkedModuleSource srcdir pretty srcs iface = - case ifaceTokenizedSrc iface of - Just tokens -> writeUtf8File path . html . render' $ tokens - Nothing -> return () + case (ifaceTokenizedSrc iface, ifaceHieFile iface) of + (Just tokens, Just hiefile) -> do + let mast = if M.size asts == 1 + then snd <$> M.lookupMin asts + else M.lookup (mkFastString file) asts + file = hsFile hiefile + asts = getAsts $ hieAST hiefile + case mast of + Just ast -> do + let types = hieTypes hiefile + flatAst = fmap (\i -> recoverFullType i types) ast + writeUtf8File path . html . render' flatAst $ tokens + Nothing -> if M.size asts == 0 + then return () + else error $ "couldn't find ast for " ++ file ++ show (M.keys asts) + _ -> return () where render' = render (Just srcCssFile) (Just highlightScript) srcs html = if pretty then renderHtml else showHtml diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Ast.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Ast.hs deleted file mode 100644 index a9ffc36e54..0000000000 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Ast.hs +++ /dev/null @@ -1,237 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE ViewPatterns #-} - -module Haddock.Backends.Hyperlinker.Ast (enrich) where - - -import qualified Haddock.Syb as Syb -import Haddock.Backends.Hyperlinker.Types - -import qualified GHC -import qualified SrcLoc -import qualified Outputable as GHC - -import Control.Applicative -import Control.Monad (guard) -import Data.Data -import qualified Data.Map.Strict as Map -import Data.Maybe - -import Prelude hiding (span) - -everythingInRenamedSource :: (Alternative f, Data x) - => (forall a. Data a => a -> f r) -> x -> f r -everythingInRenamedSource f = Syb.everythingButType @GHC.Name (<|>) f - --- | Add more detailed information to token stream using GHC API. -enrich :: GHC.RenamedSource -> [Token] -> [RichToken] -enrich src = - map $ \token -> RichToken - { rtkToken = token - , rtkDetails = enrichToken token detailsMap - } - where - detailsMap = - mkDetailsMap (concatMap ($ src) - [ variables - , types - , decls - , binds - , imports - ]) - -type LTokenDetails = [(GHC.SrcSpan, TokenDetails)] - --- | A map containing association between source locations and "details" of --- this location. --- -type DetailsMap = Map.Map Position (Span, TokenDetails) - -mkDetailsMap :: [(GHC.SrcSpan, TokenDetails)] -> DetailsMap -mkDetailsMap xs = - Map.fromListWith select_details [ (start, (span, token_details)) - | (ghc_span, token_details) <- xs - , GHC.RealSrcSpan span <- [ghc_span] - , let start = SrcLoc.realSrcSpanStart span - ] - where - -- favour token details which appear earlier in the list - select_details _new old = old - -lookupBySpan :: Span -> DetailsMap -> Maybe TokenDetails -lookupBySpan span details = do - let pos = SrcLoc.realSrcSpanStart span - (_, (tok_span, tok_details)) <- Map.lookupLE pos details - guard (tok_span `SrcLoc.containsSpan` span) - return tok_details - -enrichToken :: Token -> DetailsMap -> Maybe TokenDetails -enrichToken (Token typ _ spn) dm - | typ `elem` [TkIdentifier, TkOperator] = lookupBySpan spn dm -enrichToken _ _ = Nothing - --- | Obtain details map for variables ("normally" used identifiers). -variables :: GHC.RenamedSource -> LTokenDetails -variables = - everythingInRenamedSource (var `Syb.combine` rec) - where - var term = case cast term of - (Just ((GHC.dL->GHC.L sspan (GHC.HsVar _ name)) - :: GHC.LHsExpr GHC.GhcRn)) -> - pure (sspan, RtkVar (GHC.unLoc name)) - (Just (GHC.dL->GHC.L _ (GHC.RecordCon _ - (GHC.dL->GHC.L sspan name) _))) -> - pure (sspan, RtkVar name) - _ -> empty - rec term = case cast term of - Just (GHC.HsRecField (GHC.dL->GHC.L sspan name) - (_ :: GHC.LHsExpr GHC.GhcRn) _) -> - pure (sspan, RtkVar name) - _ -> empty - --- | Obtain details map for types. -types :: GHC.RenamedSource -> LTokenDetails -types = everythingInRenamedSource ty - where - ty :: forall a. Data a => a -> [(GHC.SrcSpan, TokenDetails)] - ty term = case cast term of - (Just ((GHC.dL->GHC.L sspan (GHC.HsTyVar _ _ name)) - :: GHC.LHsType GHC.GhcRn)) -> - pure (sspan, RtkType (GHC.unLoc name)) - (Just ((GHC.dL->GHC.L sspan (GHC.HsOpTy _ l name r)) - :: GHC.LHsType GHC.GhcRn)) -> - (sspan, RtkType (GHC.unLoc name)):(ty l ++ ty r) - _ -> empty - --- | Obtain details map for identifier bindings. --- --- That includes both identifiers bound by pattern matching or declared using --- ordinary assignment (in top-level declarations, let-expressions and where --- clauses). - -binds :: GHC.RenamedSource -> LTokenDetails -binds = everythingInRenamedSource - (fun `Syb.combine` pat `Syb.combine` tvar) - where - fun term = case cast term of - (Just (GHC.FunBind _ (GHC.dL->GHC.L sspan name) _ _ _ - :: GHC.HsBind GHC.GhcRn)) -> - pure (sspan, RtkBind name) - (Just (GHC.PatSynBind _ - (GHC.PSB _ (GHC.dL->GHC.L sspan name) args _ _))) -> - pure (sspan, RtkBind name) - ++ everythingInRenamedSource patsyn_binds args - _ -> empty - patsyn_binds term = case cast term of - (Just (GHC.L sspan (name :: GHC.Name))) -> - pure (sspan, RtkVar name) - _ -> empty - pat term = case cast term of - (Just ((GHC.dL->GHC.L sspan (GHC.VarPat _ name)) - :: GHC.LPat GHC.GhcRn)) -> - pure (sspan, RtkBind (GHC.unLoc name)) - (Just (GHC.dL->GHC.L _ - (GHC.ConPatIn (GHC.dL->GHC.L sspan name) recs))) -> - [(sspan, RtkVar name)] ++ everythingInRenamedSource rec recs - (Just (GHC.dL->GHC.L _ (GHC.AsPat _ (GHC.dL->GHC.L sspan name) _))) -> - pure (sspan, RtkBind name) - _ -> empty - rec term = case cast term of - (Just (GHC.HsRecField (GHC.dL->GHC.L sspan name) - (_ :: GHC.LPat GHC.GhcRn) _)) -> - pure (sspan, RtkVar name) - _ -> empty - tvar term = case cast term of - (Just ((GHC.dL->GHC.L sspan (GHC.UserTyVar _ name)) - :: GHC.LHsTyVarBndr GHC.GhcRn)) -> - pure (sspan, RtkBind (GHC.unLoc name)) - (Just (GHC.dL->GHC.L _ (GHC.KindedTyVar _ (GHC.dL->GHC.L sspan name) _))) -> - pure (sspan, RtkBind name) - _ -> empty - --- | Obtain details map for top-level declarations. -decls :: GHC.RenamedSource -> LTokenDetails -decls (group, _, _, _) = concatMap ($ group) - [ concat . map typ . concat . map GHC.group_tyclds . GHC.hs_tyclds - , everythingInRenamedSource fun . GHC.hs_valds - , everythingInRenamedSource fix . GHC.hs_fixds - , everythingInRenamedSource (con `Syb.combine` ins) - ] - where - typ (GHC.dL->GHC.L _ t) = case t of - GHC.DataDecl { tcdLName = name } -> pure . decl $ name - GHC.SynDecl _ name _ _ _ -> pure . decl $ name - GHC.FamDecl _ fam -> pure . decl $ GHC.fdLName fam - GHC.ClassDecl{..} -> - [decl tcdLName] - ++ concatMap sig tcdSigs - ++ concatMap tyfam tcdATs - GHC.XTyClDecl {} -> GHC.panic "haddock:decls" - fun term = case cast term of - (Just (GHC.FunBind _ (GHC.dL->GHC.L sspan name) _ _ _ - :: GHC.HsBind GHC.GhcRn)) - | GHC.isExternalName name -> pure (sspan, RtkDecl name) - (Just (GHC.PatSynBind _ (GHC.PSB _ (GHC.dL->GHC.L sspan name) _ _ _))) - | GHC.isExternalName name -> pure (sspan, RtkDecl name) - _ -> empty - con term = case cast term of - (Just (cdcl :: GHC.ConDecl GHC.GhcRn)) -> - map decl (GHC.getConNames cdcl) - ++ everythingInRenamedSource fld cdcl - Nothing -> empty - ins term = case cast term of - (Just ((GHC.DataFamInstD _ (GHC.DataFamInstDecl eqn)) - :: GHC.InstDecl GHC.GhcRn)) - -> pure . tyref $ GHC.feqn_tycon $ GHC.hsib_body eqn - (Just (GHC.TyFamInstD _ (GHC.TyFamInstDecl eqn))) -> - pure . tyref $ GHC.feqn_tycon $ GHC.hsib_body eqn - _ -> empty - fld term = case cast term of - Just (field :: GHC.ConDeclField GHC.GhcRn) - -> map (decl . fmap GHC.extFieldOcc) $ GHC.cd_fld_names field - Nothing -> empty - fix term = case cast term of - Just ((GHC.FixitySig _ names _) :: GHC.FixitySig GHC.GhcRn) - -> map (\(GHC.dL->GHC.L sspan x) -> (sspan, RtkVar x)) names - Just ((GHC.XFixitySig {}) :: GHC.FixitySig GHC.GhcRn) - -> GHC.panic "haddock:decls" - Nothing -> empty - tyfam (GHC.dL->GHC.L _ (GHC.FamilyDecl{..})) = [decl fdLName] - tyfam (GHC.dL->GHC.L _ (GHC.XFamilyDecl {})) = GHC.panic "haddock:dels" - tyfam _ = GHC.panic "tyfam: Impossible Match" - - sig (GHC.dL->GHC.L _ (GHC.TypeSig _ names _)) = map decl names - sig (GHC.dL->GHC.L _ (GHC.PatSynSig _ names _)) = map decl names - sig (GHC.dL->GHC.L _ (GHC.ClassOpSig _ _ names _)) = map decl names - sig _ = [] - decl (GHC.dL->GHC.L sspan name) = (sspan, RtkDecl name) - tyref (GHC.dL->GHC.L sspan name) = (sspan, RtkType name) - --- | Obtain details map for import declarations. --- --- This map also includes type and variable details for items in export and --- import lists. -imports :: GHC.RenamedSource -> LTokenDetails -imports src@(_, imps, _, _) = - everythingInRenamedSource ie src ++ mapMaybe (imp . GHC.unLoc) imps - where - ie term = case cast term of - (Just ((GHC.IEVar _ v) :: GHC.IE GHC.GhcRn)) -> pure $ var - $ GHC.ieLWrappedName v - (Just (GHC.IEThingAbs _ t)) -> pure $ typ $ GHC.ieLWrappedName t - (Just (GHC.IEThingAll _ t)) -> pure $ typ $ GHC.ieLWrappedName t - (Just (GHC.IEThingWith _ t _ vs _fls)) -> - [typ $ GHC.ieLWrappedName t] ++ map (var . GHC.ieLWrappedName) vs - (Just (GHC.IEModuleContents _ m)) -> pure $ modu m - _ -> empty - typ (GHC.dL->GHC.L sspan name) = (sspan, RtkType name) - var (GHC.dL->GHC.L sspan name) = (sspan, RtkVar name) - modu (GHC.dL->GHC.L sspan name) = (sspan, RtkModule name) - imp idecl - | not . GHC.ideclImplicit $ idecl = Just (modu (GHC.ideclName idecl)) - | otherwise = Nothing diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs index d7ea70a609..cb1472da87 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs @@ -1,4 +1,5 @@ {-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ViewPatterns #-} module Haddock.Backends.Hyperlinker.Renderer (render) where @@ -9,28 +10,34 @@ import Haddock.Backends.Hyperlinker.Utils import qualified GHC import qualified Name as GHC import qualified Unique as GHC +import HieTypes hiding (Span) +import HieUtils +import HieDebug import System.FilePath.Posix (()) import Data.List import Data.Maybe import qualified Data.Map as Map +import qualified Data.Set as Set +import Control.Monad (guard) import Text.XHtml (Html, HtmlAttr, (!)) import qualified Text.XHtml as Html +import FastString type StyleClass = String -render :: Maybe FilePath -> Maybe FilePath -> SrcMap -> [RichToken] +render :: Maybe FilePath -> Maybe FilePath -> SrcMap -> HieAST HieTypeFix -> [Token] -> Html -render mcss mjs srcs tokens = header mcss mjs <> body srcs tokens +render mcss mjs srcs ast tokens = header mcss mjs <> body srcs ast tokens -body :: SrcMap -> [RichToken] -> Html -body srcs tokens = Html.body . Html.pre $ hypsrc +body :: SrcMap -> HieAST HieTypeFix -> [Token] -> Html +body srcs ast tokens = Html.body . Html.pre $ hypsrc where - hypsrc = mconcat . map (richToken srcs) $ tokens + hypsrc = mconcat . map (richToken srcs ast) $ tokens header :: Maybe FilePath -> Maybe FilePath -> Html @@ -52,23 +59,63 @@ header mcss mjs = ] -- | Given information about the source position of definitions, render a token -richToken :: SrcMap -> RichToken -> Html -richToken srcs (RichToken Token{..} details) +richToken :: SrcMap -> HieAST HieTypeFix -> Token -> Html +richToken srcs ast Token{..} | tkType == TkSpace = renderSpace (GHC.srcSpanStartLine tkSpan) tkValue - | otherwise = linked content + | otherwise = annotated $ linked content where content = tokenSpan ! [ multiclass style ] tokenSpan = Html.thespan (Html.toHtml tkValue) - style = tokenStyle tkType ++ maybe [] richTokenStyle details + style = tokenStyle tkType ++ maybe [] (concatMap richTokenStyle) contexts + + details = do + ast <- selectSmallestContaining tkSpan ast + return $ nodeInfo ast + + contexts = concatMap (Set.elems . identInfo) . Map.elems . nodeIdentifiers <$> details + + identDet = details >>= + Map.lookupMin . fmap identInfo . nodeIdentifiers -- If we have name information, we can make links - linked = case details of - Just d -> externalAnchor d . internalAnchor d . hyperlink srcs d + linked = case identDet of + Just (n,d) -> externalAnchor n d . internalAnchor n d . hyperlink srcs n Nothing -> id -richTokenStyle :: TokenDetails -> [StyleClass] -richTokenStyle (RtkVar _) = ["hs-var"] -richTokenStyle (RtkType _) = ["hs-type"] + annotated = case details of + Just d -> annotate d + Nothing -> id + +annotate :: NodeInfo HieTypeFix -> Html -> Html +annotate ni content = + Html.thespan (annot <> content) ! [ Html.theclass "annot" ] + where + annot + | not (null annotation) = + Html.thespan (Html.toHtml annotation) ! [ Html.theclass "annottext" ] + | otherwise = mempty + annotation = typ ++ identTyps + typ = unlines $ map show $ nodeType ni + typedIdents = [ (n,t) | (n, identType -> Just t) <- Map.toList $ nodeIdentifiers ni ] + identTyps + | length typedIdents > 1 || null typ + = concatMap (\(n,t) -> printName n ++ " :: " ++ show t ++ "\n") typedIdents + | otherwise = "" + + printName (Right n) = unpackFS $ GHC.occNameFS $ GHC.getOccName n + printName (Left n) = GHC.moduleNameString n + + +richTokenStyle :: ContextInfo -> [StyleClass] +richTokenStyle Use = ["hs-var"] +richTokenStyle IEThing{} = ["hs-var"] +richTokenStyle TyDecl = ["hs-var"] +richTokenStyle ValBind{} = ["hs-var"] +richTokenStyle PatternBind{} = ["hs-var"] +richTokenStyle ClassTyDecl{} = ["hs-var"] +richTokenStyle RecField{} = ["hs-var"] +richTokenStyle Decl{} = ["hs-type"] +richTokenStyle TyVarBind{} = ["hs-type"] richTokenStyle _ = [] tokenStyle :: TokenType -> [StyleClass] @@ -89,15 +136,27 @@ tokenStyle TkUnknown = [] multiclass :: [StyleClass] -> HtmlAttr multiclass = Html.theclass . intercalate " " -externalAnchor :: TokenDetails -> Html -> Html -externalAnchor (RtkDecl name) content = - Html.anchor content ! [ Html.name $ externalAnchorIdent name ] -externalAnchor _ content = content - -internalAnchor :: TokenDetails -> Html -> Html -internalAnchor (RtkBind name) content = - Html.anchor content ! [ Html.name $ internalAnchorIdent name ] -internalAnchor _ content = content +externalAnchor :: Identifier -> Set.Set ContextInfo -> Html -> Html +externalAnchor (Right name) contexts content + | not (GHC.isInternalName name) + && any isBinding contexts = + Html.thespan content ! [ Html.identifier $ externalAnchorIdent name ] +externalAnchor _ _ content = content + +isBinding :: ContextInfo -> Bool +isBinding (ValBind RegularBind _ _) = True +isBinding PatternBind{} = True +isBinding Decl{} = True +isBinding (RecField RecFieldDecl _) = True +isBinding TyVarBind{} = True +isBinding ClassTyDecl{} = True +isBinding _ = False + +internalAnchor :: Identifier -> Set.Set ContextInfo -> Html -> Html +internalAnchor (Right name) contexts content + | GHC.isInternalName name && any isBinding contexts = + Html.thespan content ! [ Html.identifier $ internalAnchorIdent name ] +internalAnchor _ _ content = content externalAnchorIdent :: GHC.Name -> String externalAnchorIdent = hypSrcNameUrl @@ -105,13 +164,13 @@ externalAnchorIdent = hypSrcNameUrl internalAnchorIdent :: GHC.Name -> String internalAnchorIdent = ("local-" ++) . show . GHC.getKey . GHC.nameUnique -hyperlink :: SrcMap -> TokenDetails -> Html -> Html -hyperlink srcs details = case rtkName details of - Left name -> +hyperlink :: SrcMap -> Identifier -> Html -> Html +hyperlink srcs ident = case ident of + Right name -> if GHC.isInternalName name then internalHyperlink name else externalNameHyperlink srcs name - Right name -> externalModHyperlink srcs name + Left name -> externalModHyperlink srcs name internalHyperlink :: GHC.Name -> Html -> Html internalHyperlink name content = @@ -151,4 +210,4 @@ renderSpace line space = lineAnchor :: Int -> Html -lineAnchor line = Html.anchor Html.noHtml ! [ Html.name $ hypSrcLineUrl line ] +lineAnchor line = Html.thespan Html.noHtml ! [ Html.identifier $ hypSrcLineUrl line ] diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs index e377471eac..9ca8361e31 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs @@ -31,29 +31,6 @@ data TokenType | TkUnknown deriving (Show, Eq) - -data RichToken = RichToken - { rtkToken :: Token - , rtkDetails :: Maybe TokenDetails - } - -data TokenDetails - = RtkVar GHC.Name - | RtkType GHC.Name - | RtkBind GHC.Name - | RtkDecl GHC.Name - | RtkModule GHC.ModuleName - deriving (Eq) - - -rtkName :: TokenDetails -> Either GHC.Name GHC.ModuleName -rtkName (RtkVar name) = Left name -rtkName (RtkType name) = Left name -rtkName (RtkBind name) = Left name -rtkName (RtkDecl name) = Left name -rtkName (RtkModule name) = Right name - - -- | Path for making cross-package hyperlinks in generated sources. -- -- Used in 'SrcMap' to determine whether module originates in current package diff --git a/haddock-api/src/Haddock/Interface/Create.hs b/haddock-api/src/Haddock/Interface/Create.hs index c9290ed01f..dce00860aa 100644 --- a/haddock-api/src/Haddock/Interface/Create.hs +++ b/haddock-api/src/Haddock/Interface/Create.hs @@ -28,7 +28,6 @@ import Haddock.Utils import Haddock.Convert import Haddock.Interface.LexParseRn import Haddock.Backends.Hyperlinker.Types -import Haddock.Backends.Hyperlinker.Ast as Hyperlinker import Haddock.Backends.Hyperlinker.Parser as Hyperlinker import Data.Bifunctor @@ -49,7 +48,7 @@ import qualified Avail import qualified Module import qualified SrcLoc import ConLike (ConLike(..)) -import GHC +import GHC hiding (Token) import HscTypes import Name import NameSet @@ -62,6 +61,10 @@ import FastString ( unpackFS, fastStringToByteString) import BasicTypes ( StringLiteral(..), SourceText(..), PromotionFlag(..) ) import qualified Outputable as O +import Data.IORef +import HieTypes +import HieBin + -- | Use a 'TypecheckedModule' to produce an 'Interface'. -- To do this, we need access to already processed modules in the topological @@ -169,7 +172,7 @@ createInterface tm flags modMap instIfaceMap = do modWarn <- liftErrMsg (moduleWarning dflags gre warnings) - tokenizedSrc <- mkMaybeTokenizedSrc dflags flags tm + (tokenizedSrc,hieFile) <- mkMaybeTokenizedSrc dflags flags tm return $! Interface { ifaceMod = mdl @@ -197,6 +200,7 @@ createInterface tm flags modMap instIfaceMap = do , ifaceHaddockCoverage = coverage , ifaceWarningMap = warningMap , ifaceTokenizedSrc = tokenizedSrc + , ifaceHieFile = hieFile } @@ -1201,30 +1205,37 @@ seqList [] = () seqList (x : xs) = x `seq` seqList xs mkMaybeTokenizedSrc :: DynFlags -> [Flag] -> TypecheckedModule - -> ErrMsgGhc (Maybe [RichToken]) + -> ErrMsgGhc (Maybe [Token], Maybe HieFile) mkMaybeTokenizedSrc dflags flags tm | Flag_HyperlinkedSource `elem` flags = case renamedSource tm of Just src -> do tokens <- liftGhcToErrMsgGhc (liftIO (mkTokenizedSrc dflags summary src)) - return $ Just tokens + hiefile <- liftGhcToErrMsgGhc $ do + env <- getSession + nc <- liftIO $ readIORef $ hsc_NC env + let hiefile = ml_hie_file $ ms_location summary + (file, nc') <- liftIO $ readHieFile nc hiefile + liftIO $ writeIORef (hsc_NC env) nc' + return file + return (Just tokens,Just hiefile) Nothing -> do liftErrMsg . tell . pure $ concat [ "Warning: Cannot hyperlink module \"" , moduleNameString . ms_mod_name $ summary , "\" because renamed source is not available" ] - return Nothing - | otherwise = return Nothing + return (Nothing,Nothing) + | otherwise = return (Nothing,Nothing) where summary = pm_mod_summary . tm_parsed_module $ tm -mkTokenizedSrc :: DynFlags -> ModSummary -> RenamedSource -> IO [RichToken] +mkTokenizedSrc :: DynFlags -> ModSummary -> RenamedSource -> IO [Token] mkTokenizedSrc dflags ms src = do -- make sure to read the whole file at once otherwise -- we run out of file descriptors (see #495) rawSrc <- BS.readFile (msHsFilePath ms) >>= evaluate let tokens = Hyperlinker.parse dflags filepath (Utf8.decodeUtf8 rawSrc) - return $ Hyperlinker.enrich src tokens + return tokens where filepath = msHsFilePath ms diff --git a/haddock-api/src/Haddock/Types.hs b/haddock-api/src/Haddock/Types.hs index 2f5d0a9a0e..c58f451df7 100644 --- a/haddock-api/src/Haddock/Types.hs +++ b/haddock-api/src/Haddock/Types.hs @@ -38,7 +38,7 @@ import qualified Data.Map as Map import Documentation.Haddock.Types import BasicTypes (Fixity(..), PromotionFlag(..)) -import GHC hiding (NoLink) +import GHC hiding (NoLink, Token) import DynFlags (Language) import qualified GHC.LanguageExtensions as LangExt import OccName @@ -46,6 +46,7 @@ import Outputable import Control.Monad (ap) import Haddock.Backends.Hyperlinker.Types +import HieTypes ----------------------------------------------------------------------------- -- * Convenient synonyms @@ -144,7 +145,8 @@ data Interface = Interface -- | Tokenized source code of module (avaliable if Haddock is invoked with -- source generation flag). - , ifaceTokenizedSrc :: !(Maybe [RichToken]) + , ifaceTokenizedSrc :: !(Maybe [Token]) + , ifaceHieFile :: !(Maybe HieFile) } type WarningMap = Map Name (Doc Name) diff --git a/haddock.cabal b/haddock.cabal index 8285764a33..64a58d0a0f 100644 --- a/haddock.cabal +++ b/haddock.cabal @@ -119,7 +119,6 @@ executable haddock Haddock.Backends.HaddockDB Haddock.Backends.Hoogle Haddock.Backends.Hyperlinker - Haddock.Backends.Hyperlinker.Ast Haddock.Backends.Hyperlinker.Parser Haddock.Backends.Hyperlinker.Renderer Haddock.Backends.Hyperlinker.Types From c2a2c99d5bee7b0c41af122258e8947a235068e6 Mon Sep 17 00:00:00 2001 From: Zubin Duggal Date: Wed, 1 Aug 2018 15:34:04 +0530 Subject: [PATCH 02/21] Improve rendering, fix a few bugs --- .../Haddock/Backends/Hyperlinker/Renderer.hs | 58 +++++++++++++------ 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs index cb1472da87..f6d12b30cc 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs @@ -26,6 +26,7 @@ import Text.XHtml (Html, HtmlAttr, (!)) import qualified Text.XHtml as Html import FastString +import SrcLoc (containsSpan) type StyleClass = String @@ -37,8 +38,7 @@ render mcss mjs srcs ast tokens = header mcss mjs <> body srcs ast tokens body :: SrcMap -> HieAST HieTypeFix -> [Token] -> Html body srcs ast tokens = Html.body . Html.pre $ hypsrc where - hypsrc = mconcat . map (richToken srcs ast) $ tokens - + hypsrc = renderWithAst srcs ast tokens header :: Maybe FilePath -> Maybe FilePath -> Html header mcss mjs @@ -58,32 +58,54 @@ header mcss mjs = , Html.src scriptFile ] +splitTokens :: HieAST HieTypeFix -> [Token] -> ([Token],[Token],[Token]) +splitTokens ast toks = (before,during,after) + where + (before,rest) = break inAst toks + (during,after) = break (not . inAst) rest + inAst t = nodeSpan ast `containsSpan` tkSpan t + +renderWithAst :: SrcMap -> HieAST HieTypeFix -> [Token] -> Html +renderWithAst srcs ast toks = anchored $ case toks of + [tok] | nodeSpan ast == tkSpan tok -> richToken srcs (nodeInfo ast) tok + xs -> go (nodeChildren ast) xs + where + go _ [] = mempty + go [] xs = foldMap renderToken xs + go (cur:rest) xs = + foldMap renderToken before <> renderWithAst srcs cur during <> go rest after + where + (before,during,after) = splitTokens cur xs + anchored c = Map.foldrWithKey anchorOne c (nodeIdentifiers $ nodeInfo ast) + anchorOne n dets c = externalAnchor n d $ internalAnchor n d c + where d = identInfo dets + +renderToken :: Token -> Html +renderToken Token{..} + | tkType == TkSpace = renderSpace (GHC.srcSpanStartLine tkSpan) tkValue + | otherwise = tokenSpan ! [ multiclass style ] + where + style = tokenStyle tkType + tokenSpan = Html.thespan (Html.toHtml tkValue) + -- | Given information about the source position of definitions, render a token -richToken :: SrcMap -> HieAST HieTypeFix -> Token -> Html -richToken srcs ast Token{..} +richToken :: SrcMap -> NodeInfo HieTypeFix -> Token -> Html +richToken srcs details Token{..} | tkType == TkSpace = renderSpace (GHC.srcSpanStartLine tkSpan) tkValue - | otherwise = annotated $ linked content + | otherwise = annotate details $ linked content where content = tokenSpan ! [ multiclass style ] tokenSpan = Html.thespan (Html.toHtml tkValue) - style = tokenStyle tkType ++ maybe [] (concatMap richTokenStyle) contexts + style = tokenStyle tkType ++ concatMap richTokenStyle contexts - details = do - ast <- selectSmallestContaining tkSpan ast - return $ nodeInfo ast + contexts = concatMap (Set.elems . identInfo) . Map.elems . nodeIdentifiers $ details - contexts = concatMap (Set.elems . identInfo) . Map.elems . nodeIdentifiers <$> details - - identDet = details >>= - Map.lookupMin . fmap identInfo . nodeIdentifiers + -- pick an arbitary identifier to hyperlink with + identDet = Map.lookupMin . nodeIdentifiers $ details -- If we have name information, we can make links linked = case identDet of - Just (n,d) -> externalAnchor n d . internalAnchor n d . hyperlink srcs n - Nothing -> id - - annotated = case details of - Just d -> annotate d + Just (n,_) -> hyperlink srcs n Nothing -> id annotate :: NodeInfo HieTypeFix -> Html -> Html From 9107535c1ec368d48bbe1e3e367e1705a52bd81f Mon Sep 17 00:00:00 2001 From: Zubin Duggal Date: Thu, 2 Aug 2018 16:17:30 +0530 Subject: [PATCH 03/21] Read hie file just before hyperlinking to improve memory usage --- haddock-api/resources/html/solarized.css | 2 +- haddock-api/src/Haddock/Backends/Hyperlinker.hs | 7 ++++++- haddock-api/src/Haddock/Interface/Create.hs | 8 ++------ haddock-api/src/Haddock/Types.hs | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/haddock-api/resources/html/solarized.css b/haddock-api/resources/html/solarized.css index 1bc2aa7844..0146eeddca 100644 --- a/haddock-api/resources/html/solarized.css +++ b/haddock-api/resources/html/solarized.css @@ -90,7 +90,7 @@ span.annot:hover span.annottext{ span.annot span.annottext:before{ content: ""; position: absolute; - left: -1em; top: -2em; + left: -1em; top: -1em; background: #FFFFFF00; z-index:-1; padding: 2em 2em; diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker.hs b/haddock-api/src/Haddock/Backends/Hyperlinker.hs index 5ebfe1bc5c..9c5acdacc4 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker.hs @@ -19,9 +19,12 @@ import System.FilePath import HieTypes import HieUtils (recoverFullType) +import HieBin import Data.Map as M import FastString import HieDebug +import NameCache +import UniqSupply -- | Generate hyperlinked source for given interfaces. @@ -51,7 +54,9 @@ ppHyperlinkedModuleSource :: FilePath -> Bool -> SrcMap -> Interface -> IO () ppHyperlinkedModuleSource srcdir pretty srcs iface = case (ifaceTokenizedSrc iface, ifaceHieFile iface) of - (Just tokens, Just hiefile) -> do + (Just tokens, Just hfp) -> do + u <- mkSplitUniqSupply 'a' + (hiefile,_) <- readHieFile (initNameCache u []) hfp let mast = if M.size asts == 1 then snd <$> M.lookupMin asts else M.lookup (mkFastString file) asts diff --git a/haddock-api/src/Haddock/Interface/Create.hs b/haddock-api/src/Haddock/Interface/Create.hs index dce00860aa..c7b1e1e807 100644 --- a/haddock-api/src/Haddock/Interface/Create.hs +++ b/haddock-api/src/Haddock/Interface/Create.hs @@ -1205,18 +1205,14 @@ seqList [] = () seqList (x : xs) = x `seq` seqList xs mkMaybeTokenizedSrc :: DynFlags -> [Flag] -> TypecheckedModule - -> ErrMsgGhc (Maybe [Token], Maybe HieFile) + -> ErrMsgGhc (Maybe [Token], Maybe FilePath) mkMaybeTokenizedSrc dflags flags tm | Flag_HyperlinkedSource `elem` flags = case renamedSource tm of Just src -> do tokens <- liftGhcToErrMsgGhc (liftIO (mkTokenizedSrc dflags summary src)) hiefile <- liftGhcToErrMsgGhc $ do - env <- getSession - nc <- liftIO $ readIORef $ hsc_NC env let hiefile = ml_hie_file $ ms_location summary - (file, nc') <- liftIO $ readHieFile nc hiefile - liftIO $ writeIORef (hsc_NC env) nc' - return file + return hiefile return (Just tokens,Just hiefile) Nothing -> do liftErrMsg . tell . pure $ concat diff --git a/haddock-api/src/Haddock/Types.hs b/haddock-api/src/Haddock/Types.hs index c58f451df7..ea1e7937f5 100644 --- a/haddock-api/src/Haddock/Types.hs +++ b/haddock-api/src/Haddock/Types.hs @@ -146,7 +146,7 @@ data Interface = Interface -- | Tokenized source code of module (avaliable if Haddock is invoked with -- source generation flag). , ifaceTokenizedSrc :: !(Maybe [Token]) - , ifaceHieFile :: !(Maybe HieFile) + , ifaceHieFile :: !(Maybe FilePath) } type WarningMap = Map Name (Doc Name) From 3c884184ca8463e57fbc7f4ded6e5669140883e5 Mon Sep 17 00:00:00 2001 From: Zubin Duggal Date: Sun, 12 Aug 2018 20:25:14 +0530 Subject: [PATCH 04/21] final touches --- .../src/Haddock/Backends/Hyperlinker.hs | 13 +++-- .../Haddock/Backends/Hyperlinker/Renderer.hs | 49 ++++++++++--------- haddock-api/src/Haddock/Interface/Create.hs | 37 +------------- haddock-api/src/Haddock/Types.hs | 2 +- 4 files changed, 37 insertions(+), 64 deletions(-) diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker.hs b/haddock-api/src/Haddock/Backends/Hyperlinker.hs index 9c5acdacc4..05b8945dae 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker.hs @@ -8,8 +8,10 @@ module Haddock.Backends.Hyperlinker import Haddock.Types import Haddock.Utils (writeUtf8File) import Haddock.Backends.Hyperlinker.Renderer +import Haddock.Backends.Hyperlinker.Parser import Haddock.Backends.Hyperlinker.Types import Haddock.Backends.Hyperlinker.Utils +import Documentation.Haddock.Utf8 as Utf8 import Text.XHtml hiding (()) @@ -22,7 +24,6 @@ import HieUtils (recoverFullType) import HieBin import Data.Map as M import FastString -import HieDebug import NameCache import UniqSupply @@ -53,8 +54,8 @@ ppHyperlinkedSource outdir libdir mstyle pretty srcs ifaces = do ppHyperlinkedModuleSource :: FilePath -> Bool -> SrcMap -> Interface -> IO () ppHyperlinkedModuleSource srcdir pretty srcs iface = - case (ifaceTokenizedSrc iface, ifaceHieFile iface) of - (Just tokens, Just hfp) -> do + case ifaceHieFile iface of + (Just hfp) -> do u <- mkSplitUniqSupply 'a' (hiefile,_) <- readHieFile (initNameCache u []) hfp let mast = if M.size asts == 1 @@ -62,17 +63,19 @@ ppHyperlinkedModuleSource srcdir pretty srcs iface = else M.lookup (mkFastString file) asts file = hsFile hiefile asts = getAsts $ hieAST hiefile + df = ifaceDynFlags iface + tokens = parse df file (Utf8.decodeUtf8 $ hsSrc hiefile) case mast of Just ast -> do let types = hieTypes hiefile flatAst = fmap (\i -> recoverFullType i types) ast - writeUtf8File path . html . render' flatAst $ tokens + writeUtf8File path . html . render' df flatAst $ tokens Nothing -> if M.size asts == 0 then return () else error $ "couldn't find ast for " ++ file ++ show (M.keys asts) _ -> return () where - render' = render (Just srcCssFile) (Just highlightScript) srcs + render' df = render (Just srcCssFile) (Just highlightScript) df srcs html = if pretty then renderHtml else showHtml path = srcdir hypSrcModuleFile (ifaceMod iface) diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs index f6d12b30cc..0779dbee94 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs @@ -12,7 +12,7 @@ import qualified Name as GHC import qualified Unique as GHC import HieTypes hiding (Span) import HieUtils -import HieDebug +import DynFlags (DynFlags) import System.FilePath.Posix (()) @@ -26,19 +26,19 @@ import Text.XHtml (Html, HtmlAttr, (!)) import qualified Text.XHtml as Html import FastString -import SrcLoc (containsSpan) +import SrcLoc type StyleClass = String -render :: Maybe FilePath -> Maybe FilePath -> SrcMap -> HieAST HieTypeFix -> [Token] +render :: Maybe FilePath -> Maybe FilePath -> DynFlags -> SrcMap -> HieAST HieTypeFix -> [Token] -> Html -render mcss mjs srcs ast tokens = header mcss mjs <> body srcs ast tokens +render mcss mjs df srcs ast tokens = header mcss mjs <> body df srcs ast tokens -body :: SrcMap -> HieAST HieTypeFix -> [Token] -> Html -body srcs ast tokens = Html.body . Html.pre $ hypsrc +body :: DynFlags -> SrcMap -> HieAST HieTypeFix -> [Token] -> Html +body df srcs ast tokens = Html.body . Html.pre $ hypsrc where - hypsrc = renderWithAst srcs ast tokens + hypsrc = renderWithAst df srcs ast tokens header :: Maybe FilePath -> Maybe FilePath -> Html header mcss mjs @@ -59,21 +59,24 @@ header mcss mjs = ] splitTokens :: HieAST HieTypeFix -> [Token] -> ([Token],[Token],[Token]) -splitTokens ast toks = (before,during,after) +splitTokens ast toks' = (initial++before,during,after) where - (before,rest) = break inAst toks - (during,after) = break (not . inAst) rest - inAst t = nodeSpan ast `containsSpan` tkSpan t - -renderWithAst :: SrcMap -> HieAST HieTypeFix -> [Token] -> Html -renderWithAst srcs ast toks = anchored $ case toks of - [tok] | nodeSpan ast == tkSpan tok -> richToken srcs (nodeInfo ast) tok + (initial,toks) = span ((== fsLit "lexing") . srcSpanFile . tkSpan) toks' + (before,rest) = span leftOf toks + (during,after) = span inAst rest + leftOf t = realSrcSpanEnd (tkSpan t) <= realSrcSpanStart nodeSp + inAst t = nodeSp `containsSpan` tkSpan t + nodeSp = nodeSpan ast + +renderWithAst :: DynFlags -> SrcMap -> HieAST HieTypeFix -> [Token] -> Html +renderWithAst df srcs ast toks = anchored $ case toks of + [tok] | nodeSpan ast == tkSpan tok -> richToken df srcs (nodeInfo ast) tok xs -> go (nodeChildren ast) xs where go _ [] = mempty go [] xs = foldMap renderToken xs go (cur:rest) xs = - foldMap renderToken before <> renderWithAst srcs cur during <> go rest after + foldMap renderToken before <> renderWithAst df srcs cur during <> go rest after where (before,during,after) = splitTokens cur xs anchored c = Map.foldrWithKey anchorOne c (nodeIdentifiers $ nodeInfo ast) @@ -89,10 +92,10 @@ renderToken Token{..} tokenSpan = Html.thespan (Html.toHtml tkValue) -- | Given information about the source position of definitions, render a token -richToken :: SrcMap -> NodeInfo HieTypeFix -> Token -> Html -richToken srcs details Token{..} +richToken :: DynFlags -> SrcMap -> NodeInfo HieTypeFix -> Token -> Html +richToken df srcs details Token{..} | tkType == TkSpace = renderSpace (GHC.srcSpanStartLine tkSpan) tkValue - | otherwise = annotate details $ linked content + | otherwise = annotate df details $ linked content where content = tokenSpan ! [ multiclass style ] tokenSpan = Html.thespan (Html.toHtml tkValue) @@ -108,8 +111,8 @@ richToken srcs details Token{..} Just (n,_) -> hyperlink srcs n Nothing -> id -annotate :: NodeInfo HieTypeFix -> Html -> Html -annotate ni content = +annotate :: DynFlags -> NodeInfo HieTypeFix -> Html -> Html +annotate df ni content = Html.thespan (annot <> content) ! [ Html.theclass "annot" ] where annot @@ -117,11 +120,11 @@ annotate ni content = Html.thespan (Html.toHtml annotation) ! [ Html.theclass "annottext" ] | otherwise = mempty annotation = typ ++ identTyps - typ = unlines $ map show $ nodeType ni + typ = unlines $ map (renderHieType df) $ nodeType ni typedIdents = [ (n,t) | (n, identType -> Just t) <- Map.toList $ nodeIdentifiers ni ] identTyps | length typedIdents > 1 || null typ - = concatMap (\(n,t) -> printName n ++ " :: " ++ show t ++ "\n") typedIdents + = concatMap (\(n,t) -> printName n ++ " :: " ++ renderHieType df t ++ "\n") typedIdents | otherwise = "" printName (Right n) = unpackFS $ GHC.occNameFS $ GHC.getOccName n diff --git a/haddock-api/src/Haddock/Interface/Create.hs b/haddock-api/src/Haddock/Interface/Create.hs index c7b1e1e807..1a7572aaa0 100644 --- a/haddock-api/src/Haddock/Interface/Create.hs +++ b/haddock-api/src/Haddock/Interface/Create.hs @@ -172,8 +172,6 @@ createInterface tm flags modMap instIfaceMap = do modWarn <- liftErrMsg (moduleWarning dflags gre warnings) - (tokenizedSrc,hieFile) <- mkMaybeTokenizedSrc dflags flags tm - return $! Interface { ifaceMod = mdl , ifaceIsSig = is_sig @@ -199,8 +197,8 @@ createInterface tm flags modMap instIfaceMap = do , ifaceRnOrphanInstances = [] -- Filled in `renameInterface` , ifaceHaddockCoverage = coverage , ifaceWarningMap = warningMap - , ifaceTokenizedSrc = tokenizedSrc - , ifaceHieFile = hieFile + , ifaceHieFile = Just $ ml_hie_file $ ms_location ms + , ifaceDynFlags = dflags } @@ -1204,37 +1202,6 @@ seqList :: [a] -> () seqList [] = () seqList (x : xs) = x `seq` seqList xs -mkMaybeTokenizedSrc :: DynFlags -> [Flag] -> TypecheckedModule - -> ErrMsgGhc (Maybe [Token], Maybe FilePath) -mkMaybeTokenizedSrc dflags flags tm - | Flag_HyperlinkedSource `elem` flags = case renamedSource tm of - Just src -> do - tokens <- liftGhcToErrMsgGhc (liftIO (mkTokenizedSrc dflags summary src)) - hiefile <- liftGhcToErrMsgGhc $ do - let hiefile = ml_hie_file $ ms_location summary - return hiefile - return (Just tokens,Just hiefile) - Nothing -> do - liftErrMsg . tell . pure $ concat - [ "Warning: Cannot hyperlink module \"" - , moduleNameString . ms_mod_name $ summary - , "\" because renamed source is not available" - ] - return (Nothing,Nothing) - | otherwise = return (Nothing,Nothing) - where - summary = pm_mod_summary . tm_parsed_module $ tm - -mkTokenizedSrc :: DynFlags -> ModSummary -> RenamedSource -> IO [Token] -mkTokenizedSrc dflags ms src = do - -- make sure to read the whole file at once otherwise - -- we run out of file descriptors (see #495) - rawSrc <- BS.readFile (msHsFilePath ms) >>= evaluate - let tokens = Hyperlinker.parse dflags filepath (Utf8.decodeUtf8 rawSrc) - return tokens - where - filepath = msHsFilePath ms - -- | Find a stand-alone documentation comment by its name. findNamedDoc :: String -> [HsDecl GhcRn] -> ErrMsgM (Maybe HsDocString) findNamedDoc name = search diff --git a/haddock-api/src/Haddock/Types.hs b/haddock-api/src/Haddock/Types.hs index ea1e7937f5..d98926f47e 100644 --- a/haddock-api/src/Haddock/Types.hs +++ b/haddock-api/src/Haddock/Types.hs @@ -145,8 +145,8 @@ data Interface = Interface -- | Tokenized source code of module (avaliable if Haddock is invoked with -- source generation flag). - , ifaceTokenizedSrc :: !(Maybe [Token]) , ifaceHieFile :: !(Maybe FilePath) + , ifaceDynFlags :: !DynFlags } type WarningMap = Map Name (Doc Name) From 1c00211820d91775e70d23bca2393d465bbaa6ca Mon Sep 17 00:00:00 2001 From: Alec Theriault Date: Tue, 11 Dec 2018 07:49:36 -0800 Subject: [PATCH 05/21] Post rebase cleanup --- haddock-api/src/Haddock.hs | 2 +- haddock-api/src/Haddock/Backends/Hyperlinker.hs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/haddock-api/src/Haddock.hs b/haddock-api/src/Haddock.hs index f81e546c08..b7d2c99f4f 100644 --- a/haddock-api/src/Haddock.hs +++ b/haddock-api/src/Haddock.hs @@ -447,7 +447,7 @@ readInterfaceFiles name_cache_accessor pairs bypass_version_check = do withGhc' :: String -> [String] -> (DynFlags -> Ghc a) -> IO a withGhc' libDir flags ghcActs = runGhc (Just libDir) $ do dynflags <- getSessionDynFlags - dynflags' <- parseGhcFlags (foldl' gopt_set dynflags [Opt_IdeInfo, Opt_Haddock]) { + dynflags' <- parseGhcFlags (foldl' gopt_set dynflags [Opt_WriteHie, Opt_Haddock]) { hscTarget = HscNothing, ghcMode = CompManager, ghcLink = NoLink diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker.hs b/haddock-api/src/Haddock/Backends/Hyperlinker.hs index 05b8945dae..a0ba2e945e 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker.hs @@ -61,13 +61,13 @@ ppHyperlinkedModuleSource srcdir pretty srcs iface = let mast = if M.size asts == 1 then snd <$> M.lookupMin asts else M.lookup (mkFastString file) asts - file = hsFile hiefile - asts = getAsts $ hieAST hiefile + file = hie_hs_file hiefile + asts = getAsts $ hie_asts hiefile df = ifaceDynFlags iface - tokens = parse df file (Utf8.decodeUtf8 $ hsSrc hiefile) + tokens = parse df file (Utf8.decodeUtf8 $ hie_hs_src hiefile) case mast of Just ast -> do - let types = hieTypes hiefile + let types = hie_types hiefile flatAst = fmap (\i -> recoverFullType i types) ast writeUtf8File path . html . render' df flatAst $ tokens Nothing -> if M.size asts == 0 From 1c551c594239e1289bf66ca386db73df18476c7d Mon Sep 17 00:00:00 2001 From: Alec Theriault Date: Wed, 12 Dec 2018 14:19:54 -0800 Subject: [PATCH 06/21] .hie files get put in the right output directory Move the logic for handling temporary output directories up and make .hie files write to there. Although having shorter lived temp directories is better, they were so short lived that the .hie files were gone by the time the hyperlinker backend was ready to run! --- haddock-api/src/Haddock.hs | 23 +++++++++++- haddock-api/src/Haddock/GhcUtils.hs | 5 ++- haddock-api/src/Haddock/Interface.hs | 55 +++++----------------------- haddock-api/src/Haddock/Utils.hs | 8 +++- 4 files changed, 42 insertions(+), 49 deletions(-) diff --git a/haddock-api/src/Haddock.hs b/haddock-api/src/Haddock.hs index b7d2c99f4f..e8eacea17d 100644 --- a/haddock-api/src/Haddock.hs +++ b/haddock-api/src/Haddock.hs @@ -39,6 +39,7 @@ import Haddock.Version import Haddock.InterfaceFile import Haddock.Options import Haddock.Utils +import Haddock.GhcUtils (modifySessionDynFlags, setOutputDir) import Control.Monad hiding (forM_) import Data.Foldable (forM_, foldl') @@ -66,6 +67,8 @@ import qualified GHC.Paths as GhcPaths import Paths_haddock_api (getDataDir) import System.Directory (doesDirectoryExist) #endif +import System.Directory (getTemporaryDirectory) +import System.FilePath (()) import Text.ParserCombinators.ReadP (readP_to_S) import GHC hiding (verbosity) @@ -164,6 +167,15 @@ haddockWithGhc ghc args = handleTopExceptions $ do -- bypass the interface version check let noChecks = Flag_BypassInterfaceVersonCheck `elem` flags + -- Create a temporary directory and redirect GHC output there (unless user + -- requested otherwise). + -- + -- Output dir needs to be set before calling 'depanal' since 'depanal' uses it + -- to compute output file names that are stored in the 'DynFlags' of the + -- resulting 'ModSummary's. + let withDir | Flag_NoTmpCompDir `elem` flags = id + | otherwise = withTempOutputDir + unless (Flag_NoWarnings `elem` flags) $ do hypSrcWarnings flags forM_ (warnings args) $ \warning -> do @@ -171,7 +183,7 @@ haddockWithGhc ghc args = handleTopExceptions $ do when noChecks $ hPutStrLn stderr noCheckWarning - ghc flags' $ do + ghc flags' $ withDir $ do dflags <- getDynFlags forM_ (optShowInterfaceFile flags) $ \path -> liftIO $ do @@ -202,6 +214,15 @@ haddockWithGhc ghc args = handleTopExceptions $ do -- Render even though there are no input files (usually contents/index). liftIO $ renderStep dflags flags sinceQual qual packages [] +-- | Run the GHC action using a temporary output directory +withTempOutputDir :: Ghc a -> Ghc a +withTempOutputDir action = do + tmp <- liftIO getTemporaryDirectory + x <- liftIO getProcessID + let dir = tmp ".haddock-" ++ show x + modifySessionDynFlags (setOutputDir dir) + withTempDir dir action + -- | Create warnings about potential misuse of -optghc warnings :: [String] -> [String] warnings = map format . filter (isPrefixOf "-optghc") diff --git a/haddock-api/src/Haddock/GhcUtils.hs b/haddock-api/src/Haddock/GhcUtils.hs index cdaf6ae48f..e9e06625f8 100644 --- a/haddock-api/src/Haddock/GhcUtils.hs +++ b/haddock-api/src/Haddock/GhcUtils.hs @@ -413,11 +413,12 @@ minimalDef n = do ------------------------------------------------------------------------------- -setObjectDir, setHiDir, setStubDir, setOutputDir :: String -> DynFlags -> DynFlags +setObjectDir, setHiDir, setHieDir, setStubDir, setOutputDir :: String -> DynFlags -> DynFlags setObjectDir f d = d{ objectDir = Just f} setHiDir f d = d{ hiDir = Just f} +setHieDir f d = d{ hieDir = Just f} setStubDir f d = d{ stubDir = Just f , includePaths = addGlobalInclude (includePaths d) [f] } -- -stubdir D adds an implicit -I D, so that gcc can find the _stub.h file -- \#included from the .hc file when compiling with -fvia-C. -setOutputDir f = setObjectDir f . setHiDir f . setStubDir f +setOutputDir f = setObjectDir f . setHiDir f . setHieDir f . setStubDir f diff --git a/haddock-api/src/Haddock/Interface.hs b/haddock-api/src/Haddock/Interface.hs index 8bfc249c25..f1b2d45e89 100644 --- a/haddock-api/src/Haddock/Interface.hs +++ b/haddock-api/src/Haddock/Interface.hs @@ -43,18 +43,16 @@ import Haddock.Types import Haddock.Utils import Control.Monad +import Control.Exception (evaluate) import Data.List import qualified Data.Map as Map import qualified Data.Set as Set import Distribution.Verbosity -import System.Directory -import System.FilePath import Text.Printf import Module (mkModuleSet, emptyModuleSet, unionModuleSet, ModuleSet) import Digraph import DynFlags hiding (verbosity) -import Exception import GHC hiding (verbosity) import HscTypes import FastString (unpackFS) @@ -90,7 +88,7 @@ processModules verbosity modules flags extIfaces = do out verbosity verbose "Creating interfaces..." let instIfaceMap = Map.fromList [ (instMod iface, iface) | ext <- extIfaces , iface <- ifInstalledIfaces ext ] - (interfaces, ms) <- createIfaces0 verbosity modules flags instIfaceMap + (interfaces, ms) <- createIfaces verbosity modules flags instIfaceMap let exportedNames = Set.unions $ map (Set.fromList . ifaceExports) $ @@ -123,39 +121,15 @@ processModules verbosity modules flags extIfaces = do -------------------------------------------------------------------------------- -createIfaces0 :: Verbosity -> [String] -> [Flag] -> InstIfaceMap -> Ghc ([Interface], ModuleSet) -createIfaces0 verbosity modules flags instIfaceMap = - -- Output dir needs to be set before calling depanal since depanal uses it to - -- compute output file names that are stored in the DynFlags of the - -- resulting ModSummaries. - (if useTempDir then withTempOutputDir else id) $ do - modGraph <- depAnalysis - createIfaces verbosity flags instIfaceMap modGraph +createIfaces :: Verbosity -> [String] -> [Flag] -> InstIfaceMap -> Ghc ([Interface], ModuleSet) +createIfaces verbosity modules flags instIfaceMap = do + -- Ask GHC to tell us what the module graph is + targets <- mapM (\filePath -> guessTarget filePath Nothing) modules + setTargets targets + modGraph <- depanal [] False - where - useTempDir :: Bool - useTempDir = Flag_NoTmpCompDir `notElem` flags - - - withTempOutputDir :: Ghc a -> Ghc a - withTempOutputDir action = do - tmp <- liftIO getTemporaryDirectory - x <- liftIO getProcessID - let dir = tmp ".haddock-" ++ show x - modifySessionDynFlags (setOutputDir dir) - withTempDir dir action - - - depAnalysis :: Ghc ModuleGraph - depAnalysis = do - targets <- mapM (\f -> guessTarget f Nothing) modules - setTargets targets - depanal [] False - - -createIfaces :: Verbosity -> [Flag] -> InstIfaceMap -> ModuleGraph -> Ghc ([Interface], ModuleSet) -createIfaces verbosity flags instIfaceMap mods = do - let sortedMods = flattenSCCs $ topSortModuleGraph False mods Nothing + -- Visit modules in that order + let sortedMods = flattenSCCs $ topSortModuleGraph False modGraph Nothing out verbosity normal "Haddock coverage:" (ifaces, _, !ms) <- foldM f ([], Map.empty, emptyModuleSet) sortedMods return (reverse ifaces, ms) @@ -263,12 +237,3 @@ buildHomeLinks ifaces = foldl upd Map.empty (reverse ifaces) keep_old env n = Map.insertWith (\_ old -> old) n mdl env keep_new env n = Map.insert n mdl env - --------------------------------------------------------------------------------- --- * Utils --------------------------------------------------------------------------------- - - -withTempDir :: (ExceptionMonad m) => FilePath -> m a -> m a -withTempDir dir = gbracket_ (liftIO $ createDirectory dir) - (liftIO $ removeDirectoryRecursive dir) diff --git a/haddock-api/src/Haddock/Utils.hs b/haddock-api/src/Haddock/Utils.hs index 49a8271713..dda42cea8d 100644 --- a/haddock-api/src/Haddock/Utils.hs +++ b/haddock-api/src/Haddock/Utils.hs @@ -33,7 +33,7 @@ module Haddock.Utils ( -- * Miscellaneous utilities getProgramName, bye, die, dieMsg, noDieMsg, mapSnd, mapMaybeM, escapeStr, - writeUtf8File, + writeUtf8File, withTempDir, -- * HTML cross reference mapping html_xrefs_ref, html_xrefs_ref', @@ -62,6 +62,7 @@ import Haddock.Types import Haddock.GhcUtils import BasicTypes ( PromotionFlag(..) ) +import Exception (ExceptionMonad) import GHC import Name import Outputable ( panic ) @@ -76,6 +77,7 @@ import Data.List ( isSuffixOf ) import Data.Maybe ( mapMaybe ) import System.Environment ( getProgName ) import System.Exit +import System.Directory ( createDirectory, removeDirectoryRecursive ) import System.IO ( hPutStr, hSetEncoding, IOMode(..), stderr, utf8, withFile ) import System.IO.Unsafe ( unsafePerformIO ) import qualified System.FilePath.Posix as HtmlPath @@ -406,6 +408,10 @@ writeUtf8File filepath contents = withFile filepath WriteMode $ \h -> do hSetEncoding h utf8 hPutStr h contents +withTempDir :: (ExceptionMonad m) => FilePath -> m a -> m a +withTempDir dir = gbracket_ (liftIO $ createDirectory dir) + (liftIO $ removeDirectoryRecursive dir) + ----------------------------------------------------------------------------- -- * HTML cross references -- From ee5d605524dd6a844daec45f8c85b06f0b60351b Mon Sep 17 00:00:00 2001 From: Alec Theriault Date: Wed, 12 Dec 2018 14:37:08 -0800 Subject: [PATCH 07/21] Only generate .hie files for --hyperlinked-source --- haddock-api/src/Haddock.hs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/haddock-api/src/Haddock.hs b/haddock-api/src/Haddock.hs index e8eacea17d..358e5c3a4d 100644 --- a/haddock-api/src/Haddock.hs +++ b/haddock-api/src/Haddock.hs @@ -242,8 +242,9 @@ withGhc flags action = do let handleSrcErrors action' = flip handleSourceError action' $ \err -> do printException err liftIO exitFailure + needHieFiles = Flag_HyperlinkedSource `elem` flags - withGhc' libDir (ghcFlags flags) (\_ -> handleSrcErrors action) + withGhc' libDir needHieFiles (ghcFlags flags) (\_ -> handleSrcErrors action) readPackagesAndProcessModules :: [Flag] -> [String] @@ -465,14 +466,10 @@ readInterfaceFiles name_cache_accessor pairs bypass_version_check = do -- | Start a GHC session with the -haddock flag set. Also turn off -- compilation and linking. Then run the given 'Ghc' action. -withGhc' :: String -> [String] -> (DynFlags -> Ghc a) -> IO a -withGhc' libDir flags ghcActs = runGhc (Just libDir) $ do - dynflags <- getSessionDynFlags - dynflags' <- parseGhcFlags (foldl' gopt_set dynflags [Opt_WriteHie, Opt_Haddock]) { - hscTarget = HscNothing, - ghcMode = CompManager, - ghcLink = NoLink - } +withGhc' :: String -> Bool -> [String] -> (DynFlags -> Ghc a) -> IO a +withGhc' libDir needHieFiles flags ghcActs = runGhc (Just libDir) $ do + dynflags' <- parseGhcFlags =<< getSessionDynFlags + -- We disable pattern match warnings because than can be very -- expensive to check let dynflags'' = unsetPatternMatchWarnings $ @@ -503,11 +500,19 @@ withGhc' libDir flags ghcActs = runGhc (Just libDir) $ do parseGhcFlags dynflags = do -- TODO: handle warnings? - let flags' = filterRtsFlags flags - (dynflags', rest, _) <- parseDynamicFlags dynflags (map noLoc flags') + let extra_opts | needHieFiles = [Opt_WriteHie, Opt_Haddock] + | otherwise = [Opt_Haddock] + dynflags' = (foldl' gopt_set dynflags extra_opts) + { hscTarget = HscNothing + , ghcMode = CompManager + , ghcLink = NoLink + } + flags' = filterRtsFlags flags + + (dynflags'', rest, _) <- parseDynamicFlags dynflags' (map noLoc flags') if not (null rest) then throwE ("Couldn't parse GHC options: " ++ unwords flags') - else return dynflags' + else return dynflags'' unsetPatternMatchWarnings :: DynFlags -> DynFlags unsetPatternMatchWarnings dflags = From 168b90b47fc0d006a905b47f3f44db8b34a1cf56 Mon Sep 17 00:00:00 2001 From: Alec Theriault Date: Sat, 15 Dec 2018 07:11:59 -0800 Subject: [PATCH 08/21] Clean up imports --- haddock-api/haddock-api.cabal | 1 - .../src/Haddock/Backends/Hyperlinker.hs | 15 ++-- .../Haddock/Backends/Hyperlinker/Parser.hs | 4 +- .../Haddock/Backends/Hyperlinker/Renderer.hs | 80 +++++++++---------- haddock-api/src/Haddock/Interface/Create.hs | 11 +-- haddock-api/src/Haddock/Types.hs | 14 ++-- 6 files changed, 56 insertions(+), 69 deletions(-) diff --git a/haddock-api/haddock-api.cabal b/haddock-api/haddock-api.cabal index e69f2ae28e..7e8f38800e 100644 --- a/haddock-api/haddock-api.cabal +++ b/haddock-api/haddock-api.cabal @@ -129,7 +129,6 @@ test-suite spec Haddock Haddock.Backends.Hoogle Haddock.Backends.Hyperlinker - Haddock.Backends.Hyperlinker.Ast Haddock.Backends.Hyperlinker.Renderer Haddock.Backends.Hyperlinker.Utils Haddock.Backends.LaTeX diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker.hs b/haddock-api/src/Haddock/Backends/Hyperlinker.hs index a0ba2e945e..757ca66025 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker.hs @@ -63,19 +63,20 @@ ppHyperlinkedModuleSource srcdir pretty srcs iface = else M.lookup (mkFastString file) asts file = hie_hs_file hiefile asts = getAsts $ hie_asts hiefile - df = ifaceDynFlags iface tokens = parse df file (Utf8.decodeUtf8 $ hie_hs_src hiefile) case mast of - Just ast -> do + Just ast -> let types = hie_types hiefile flatAst = fmap (\i -> recoverFullType i types) ast - writeUtf8File path . html . render' df flatAst $ tokens - Nothing -> if M.size asts == 0 - then return () - else error $ "couldn't find ast for " ++ file ++ show (M.keys asts) + in writeUtf8File path . html . render' flatAst $ tokens + Nothing + | M.size asts == 0 -> return () + | otherwise -> error $ unwords [ "couldn't find ast for" + , file, show (M.keys asts) ] _ -> return () where - render' df = render (Just srcCssFile) (Just highlightScript) df srcs + df = ifaceDynFlags iface + render' = render (Just srcCssFile) (Just highlightScript) df srcs html = if pretty then renderHtml else showHtml path = srcdir hypSrcModuleFile (ifaceMod iface) diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs index f84942420d..53bdc3bf48 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs @@ -8,7 +8,7 @@ import qualified Text.Read as R import GHC ( DynFlags, addSourceToTokens ) import SrcLoc -import FastString ( mkFastString ) +import FastString ( fsLit, mkFastString ) import StringBuffer ( stringToStringBuffer ) import Lexer ( Token(..) ) import qualified Lexer as L @@ -161,7 +161,7 @@ spanToNewline n (c:str) = ghcToks :: [(Located L.Token, String)] -> [T.Token] ghcToks = reverse . (\(_,ts,_) -> ts) . foldl' go (start, [], False) where - start = mkRealSrcLoc (mkFastString "lexing") 1 1 + start = mkRealSrcLoc (fsLit "lexing") 1 1 go :: (RealSrcLoc, [T.Token], Bool) -- ^ current position, tokens accumulated, currently in pragma (or not) diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs index 0779dbee94..82992b5162 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs @@ -7,32 +7,35 @@ module Haddock.Backends.Hyperlinker.Renderer (render) where import Haddock.Backends.Hyperlinker.Types import Haddock.Backends.Hyperlinker.Utils -import qualified GHC -import qualified Name as GHC -import qualified Unique as GHC -import HieTypes hiding (Span) -import HieUtils -import DynFlags (DynFlags) +import DynFlags ( DynFlags ) +import FastString (fsLit) +import HieTypes +import HieUtils ( renderHieType ) +import Module ( ModuleName, moduleName, moduleNameString ) +import Name ( getOccString, isInternalName, Name, nameModule, nameUnique ) +import SrcLoc +import Unique ( getKey ) import System.FilePath.Posix (()) -import Data.List -import Data.Maybe import qualified Data.Map as Map import qualified Data.Set as Set -import Control.Monad (guard) import Text.XHtml (Html, HtmlAttr, (!)) import qualified Text.XHtml as Html -import FastString -import SrcLoc type StyleClass = String - -render :: Maybe FilePath -> Maybe FilePath -> DynFlags -> SrcMap -> HieAST HieTypeFix -> [Token] - -> Html +-- | Produce the HTML corresponding to a hyperlinked Haskell source +render + :: Maybe FilePath -- ^ path to the CSS file + -> Maybe FilePath -- ^ path to the JS file + -> DynFlags -- ^ used to render types + -> SrcMap -- ^ Paths to sources + -> HieAST HieTypeFix -- ^ ASTs from @.hie@ files + -> [Token] -- ^ tokens to render + -> Html render mcss mjs df srcs ast tokens = header mcss mjs <> body df srcs ast tokens body :: DynFlags -> SrcMap -> HieAST HieTypeFix -> [Token] -> Html @@ -41,10 +44,8 @@ body df srcs ast tokens = Html.body . Html.pre $ hypsrc hypsrc = renderWithAst df srcs ast tokens header :: Maybe FilePath -> Maybe FilePath -> Html -header mcss mjs - | isNothing mcss && isNothing mjs = Html.noHtml -header mcss mjs = - Html.header $ css mcss <> js mjs +header Nothing Nothing = Html.noHtml +header mcss mjs = Html.header $ css mcss <> js mjs where css Nothing = Html.noHtml css (Just cssFile) = Html.thelink Html.noHtml ! @@ -85,7 +86,7 @@ renderWithAst df srcs ast toks = anchored $ case toks of renderToken :: Token -> Html renderToken Token{..} - | tkType == TkSpace = renderSpace (GHC.srcSpanStartLine tkSpan) tkValue + | tkType == TkSpace = renderSpace (srcSpanStartLine tkSpan) tkValue | otherwise = tokenSpan ! [ multiclass style ] where style = tokenStyle tkType @@ -94,7 +95,7 @@ renderToken Token{..} -- | Given information about the source position of definitions, render a token richToken :: DynFlags -> SrcMap -> NodeInfo HieTypeFix -> Token -> Html richToken df srcs details Token{..} - | tkType == TkSpace = renderSpace (GHC.srcSpanStartLine tkSpan) tkValue + | tkType == TkSpace = renderSpace (srcSpanStartLine tkSpan) tkValue | otherwise = annotate df details $ linked content where content = tokenSpan ! [ multiclass style ] @@ -127,8 +128,8 @@ annotate df ni content = = concatMap (\(n,t) -> printName n ++ " :: " ++ renderHieType df t ++ "\n") typedIdents | otherwise = "" - printName (Right n) = unpackFS $ GHC.occNameFS $ GHC.getOccName n - printName (Left n) = GHC.moduleNameString n + printName :: Either ModuleName Name -> String + printName = either moduleNameString getOccString richTokenStyle :: ContextInfo -> [StyleClass] @@ -159,13 +160,13 @@ tokenStyle TkPragma = ["hs-pragma"] tokenStyle TkUnknown = [] multiclass :: [StyleClass] -> HtmlAttr -multiclass = Html.theclass . intercalate " " +multiclass = Html.theclass . unwords externalAnchor :: Identifier -> Set.Set ContextInfo -> Html -> Html externalAnchor (Right name) contexts content - | not (GHC.isInternalName name) - && any isBinding contexts = - Html.thespan content ! [ Html.identifier $ externalAnchorIdent name ] + | not (isInternalName name) + , any isBinding contexts + = Html.thespan content ! [ Html.identifier $ externalAnchorIdent name ] externalAnchor _ _ content = content isBinding :: ContextInfo -> Bool @@ -179,29 +180,28 @@ isBinding _ = False internalAnchor :: Identifier -> Set.Set ContextInfo -> Html -> Html internalAnchor (Right name) contexts content - | GHC.isInternalName name && any isBinding contexts = - Html.thespan content ! [ Html.identifier $ internalAnchorIdent name ] + | isInternalName name + , any isBinding contexts + = Html.thespan content ! [ Html.identifier $ internalAnchorIdent name ] internalAnchor _ _ content = content -externalAnchorIdent :: GHC.Name -> String +externalAnchorIdent :: Name -> String externalAnchorIdent = hypSrcNameUrl -internalAnchorIdent :: GHC.Name -> String -internalAnchorIdent = ("local-" ++) . show . GHC.getKey . GHC.nameUnique +internalAnchorIdent :: Name -> String +internalAnchorIdent = ("local-" ++) . show . getKey . nameUnique hyperlink :: SrcMap -> Identifier -> Html -> Html hyperlink srcs ident = case ident of - Right name -> - if GHC.isInternalName name - then internalHyperlink name - else externalNameHyperlink srcs name + Right name | isInternalName name -> internalHyperlink name + | otherwise -> externalNameHyperlink srcs name Left name -> externalModHyperlink srcs name -internalHyperlink :: GHC.Name -> Html -> Html +internalHyperlink :: Name -> Html -> Html internalHyperlink name content = Html.anchor content ! [ Html.href $ "#" ++ internalAnchorIdent name ] -externalNameHyperlink :: SrcMap -> GHC.Name -> Html -> Html +externalNameHyperlink :: SrcMap -> Name -> Html -> Html externalNameHyperlink srcs name content = case Map.lookup mdl srcs of Just SrcLocal -> Html.anchor content ! [ Html.href $ hypSrcModuleNameUrl mdl name ] @@ -209,11 +209,11 @@ externalNameHyperlink srcs name content = case Map.lookup mdl srcs of [ Html.href $ path hypSrcModuleNameUrl mdl name ] Nothing -> content where - mdl = GHC.nameModule name + mdl = nameModule name -externalModHyperlink :: SrcMap -> GHC.ModuleName -> Html -> Html +externalModHyperlink :: SrcMap -> ModuleName -> Html -> Html externalModHyperlink srcs name content = - let srcs' = Map.mapKeys GHC.moduleName srcs in + let srcs' = Map.mapKeys moduleName srcs in case Map.lookup name srcs' of Just SrcLocal -> Html.anchor content ! [ Html.href $ hypSrcModuleUrl' name ] diff --git a/haddock-api/src/Haddock/Interface/Create.hs b/haddock-api/src/Haddock/Interface/Create.hs index 1a7572aaa0..36cfeaca91 100644 --- a/haddock-api/src/Haddock/Interface/Create.hs +++ b/haddock-api/src/Haddock/Interface/Create.hs @@ -20,26 +20,21 @@ module Haddock.Interface.Create (createInterface) where import Documentation.Haddock.Doc (metaDocAppend) -import Documentation.Haddock.Utf8 as Utf8 import Haddock.Types import Haddock.Options import Haddock.GhcUtils import Haddock.Utils import Haddock.Convert import Haddock.Interface.LexParseRn -import Haddock.Backends.Hyperlinker.Types -import Haddock.Backends.Hyperlinker.Parser as Hyperlinker import Data.Bifunctor import Data.Bitraversable -import qualified Data.ByteString as BS import qualified Data.Map as M import Data.Map (Map) import Data.List import Data.Maybe import Data.Ord import Control.Applicative -import Control.Exception (evaluate) import Control.Monad import Data.Traversable @@ -48,7 +43,7 @@ import qualified Avail import qualified Module import qualified SrcLoc import ConLike (ConLike(..)) -import GHC hiding (Token) +import GHC import HscTypes import Name import NameSet @@ -61,10 +56,6 @@ import FastString ( unpackFS, fastStringToByteString) import BasicTypes ( StringLiteral(..), SourceText(..), PromotionFlag(..) ) import qualified Outputable as O -import Data.IORef -import HieTypes -import HieBin - -- | Use a 'TypecheckedModule' to produce an 'Interface'. -- To do this, we need access to already processed modules in the topological diff --git a/haddock-api/src/Haddock/Types.hs b/haddock-api/src/Haddock/Types.hs index d98926f47e..f97a335f49 100644 --- a/haddock-api/src/Haddock/Types.hs +++ b/haddock-api/src/Haddock/Types.hs @@ -30,23 +30,19 @@ module Haddock.Types ( import Control.Exception import Control.Arrow hiding ((<+>)) import Control.DeepSeq +import Control.Monad (ap) import Control.Monad.IO.Class (MonadIO(..)) -import Data.Typeable +import Data.Typeable (Typeable) import Data.Map (Map) import Data.Data (Data) -import qualified Data.Map as Map import Documentation.Haddock.Types import BasicTypes (Fixity(..), PromotionFlag(..)) -import GHC hiding (NoLink, Token) +import GHC import DynFlags (Language) import qualified GHC.LanguageExtensions as LangExt import OccName import Outputable -import Control.Monad (ap) - -import Haddock.Backends.Hyperlinker.Types -import HieTypes ----------------------------------------------------------------------------- -- * Convenient synonyms @@ -153,7 +149,7 @@ type WarningMap = Map Name (Doc Name) -- | A subset of the fields of 'Interface' that we store in the interface --- files. +-- files (these files usually have a @.haddock@ suffix). data InstalledInterface = InstalledInterface { -- | The module represented by this interface. @@ -277,7 +273,7 @@ type DocForDecl name = (Documentation name, FnArgsDoc name) noDocForDecl :: DocForDecl name -noDocForDecl = (Documentation Nothing Nothing, Map.empty) +noDocForDecl = (Documentation Nothing Nothing, mempty) ----------------------------------------------------------------------------- From b7c56043c8512b63aac7965ebe5cf66d43f78e08 Mon Sep 17 00:00:00 2001 From: Alec Theriault Date: Mon, 17 Dec 2018 13:00:57 -0800 Subject: [PATCH 09/21] Fix linking of backticked idents / parenthesized operators This is working around a quirk of the GHC lexer. It seems to be working well enough though. See the attached test case for more motivation. --- .../Haddock/Backends/Hyperlinker/Renderer.hs | 29 +++++++++++++++++++ .../src/Haddock/Backends/Hyperlinker/Types.hs | 6 +++- hypsrc-test/src/LinkingIdentifiers.hs | 14 +++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 hypsrc-test/src/LinkingIdentifiers.hs diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs index 82992b5162..880fb27a01 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs @@ -1,5 +1,6 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE PatternSynonyms #-} module Haddock.Backends.Hyperlinker.Renderer (render) where @@ -69,9 +70,37 @@ splitTokens ast toks' = (initial++before,during,after) inAst t = nodeSp `containsSpan` tkSpan t nodeSp = nodeSpan ast +-- | Turn a list of tokens into hyperlinked sources, threading in relevant link +-- information from the 'HieAST'. renderWithAst :: DynFlags -> SrcMap -> HieAST HieTypeFix -> [Token] -> Html renderWithAst df srcs ast toks = anchored $ case toks of [tok] | nodeSpan ast == tkSpan tok -> richToken df srcs (nodeInfo ast) tok + + -- NB: the GHC lexer lexes backquoted identifiers and parenthesized operators + -- as multiple tokens. + -- + -- * @a `elem` b@ turns into @[a, `, elem, `, b]@ (excluding space tokens) + -- * @(+) 1 2@ turns into @[(, +, ), 1, 2]@ (excluding space tokens) + -- + -- However, the HIE ast considers @`elem`@ and @(+)@ to be single nodes. In + -- order to make sure these get hyperlinked properly, we intercept these + -- special sequences of tokens and turn merge then into just one identifier + -- or operator token. + [BacktickTok s1, tok @ Token{ tkType = TkIdentifier }, BacktickTok s2] + | realSrcSpanStart s1 == realSrcSpanStart (nodeSpan ast) + , realSrcSpanEnd s2 == realSrcSpanEnd (nodeSpan ast) + -> richToken df srcs (nodeInfo ast) + (Token{ tkValue = "`" ++ tkValue tok ++ "`" + , tkType = TkOperator + , tkSpan = nodeSpan ast }) + [OpenParenTok s1, tok @ Token{ tkType = TkOperator }, CloseParenTok s2] + | realSrcSpanStart s1 == realSrcSpanStart (nodeSpan ast) + , realSrcSpanEnd s2 == realSrcSpanEnd (nodeSpan ast) + -> richToken df srcs (nodeInfo ast) + (Token{ tkValue = "(" ++ tkValue tok ++ ")" + , tkType = TkOperator + , tkSpan = nodeSpan ast }) + xs -> go (nodeChildren ast) xs where go _ [] = mempty diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs index 9ca8361e31..85bb8a7547 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs @@ -1,6 +1,6 @@ +{-# LANGUAGE PatternSynonyms #-} module Haddock.Backends.Hyperlinker.Types where - import qualified GHC import Data.Map (Map) @@ -12,6 +12,10 @@ data Token = Token } deriving (Show) +pattern BacktickTok sp = Token TkSpecial "`" sp +pattern OpenParenTok sp = Token TkSpecial "(" sp +pattern CloseParenTok sp = Token TkSpecial ")" sp + type Position = GHC.RealSrcLoc type Span = GHC.RealSrcSpan diff --git a/hypsrc-test/src/LinkingIdentifiers.hs b/hypsrc-test/src/LinkingIdentifiers.hs new file mode 100644 index 0000000000..4fff9776c9 --- /dev/null +++ b/hypsrc-test/src/LinkingIdentifiers.hs @@ -0,0 +1,14 @@ +-- Tests that the identifers/operators are properly linked even when: +-- +-- * backquoted, parenthesized, vanilla +-- * qualified, not-qualified +-- +module LinkingIdentifiers where + +ident :: Int -> Int -> Int +x `ident` 2 = (x `ident` 2) + (x `LinkingIdentifiers.ident` 2) +ident x 2 = ident x 2 + LinkingIdentifiers.ident x 2 + +(++:++) :: Int -> Int -> Int +x ++:++ 2 = (x ++:++ 2) + (x LinkingIdentifiers.++:++ 2) +(++:++) x 2 = (++:++) x 2 + (LinkingIdentifiers.++:++) x 2 From 4320c2767ffd962d408e7df62ab0be3ba4ca5ddb Mon Sep 17 00:00:00 2001 From: Alec Theriault Date: Thu, 20 Dec 2018 12:17:38 -0500 Subject: [PATCH 10/21] Fix inter-package source links in Hyperlinker This fixes #496. --- haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs | 2 +- haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs | 1 + haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs | 4 +++- haddock-api/src/Haddock/Types.hs | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs index 880fb27a01..c7b2be0f7e 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs @@ -235,7 +235,7 @@ externalNameHyperlink srcs name content = case Map.lookup mdl srcs of Just SrcLocal -> Html.anchor content ! [ Html.href $ hypSrcModuleNameUrl mdl name ] Just (SrcExternal path) -> Html.anchor content ! - [ Html.href $ path hypSrcModuleNameUrl mdl name ] + [ Html.href $ spliceURL Nothing (Just mdl) (Just name) Nothing (".." path) ] Nothing -> content where mdl = nameModule name diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs index 85bb8a7547..72c896b4f4 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs @@ -12,6 +12,7 @@ data Token = Token } deriving (Show) +pattern BacktickTok, OpenParenTok, CloseParenTok :: Span -> Token pattern BacktickTok sp = Token TkSpecial "`" sp pattern OpenParenTok sp = Token TkSpecial "(" sp pattern CloseParenTok sp = Token TkSpecial ")" sp diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs index 9de4a03d1d..20b0adedd3 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs @@ -6,6 +6,7 @@ module Haddock.Backends.Hyperlinker.Utils , hypSrcModuleNameUrl, hypSrcModuleLineUrl , hypSrcModuleUrlFormat , hypSrcModuleNameUrlFormat, hypSrcModuleLineUrlFormat + , spliceURL ) where @@ -13,6 +14,7 @@ import Haddock.Backends.Xhtml.Utils import GHC import FastString +import Name (nameModule_maybe) import System.FilePath.Posix (()) @@ -34,7 +36,7 @@ hypSrcModuleUrl' = hypSrcModuleFile' hypSrcNameUrl :: Name -> String hypSrcNameUrl name = spliceURL - Nothing Nothing (Just name) Nothing nameFormat + Nothing (nameModule_maybe name) (Just name) Nothing nameFormat hypSrcLineUrl :: Int -> String hypSrcLineUrl line = spliceURL diff --git a/haddock-api/src/Haddock/Types.hs b/haddock-api/src/Haddock/Types.hs index f97a335f49..a4ef5f8203 100644 --- a/haddock-api/src/Haddock/Types.hs +++ b/haddock-api/src/Haddock/Types.hs @@ -149,7 +149,7 @@ type WarningMap = Map Name (Doc Name) -- | A subset of the fields of 'Interface' that we store in the interface --- files (these files usually have a @.haddock@ suffix). +-- files. data InstalledInterface = InstalledInterface { -- | The module represented by this interface. From b17396dcc66efac510c611526fe04e9c4cfa686a Mon Sep 17 00:00:00 2001 From: Alec Theriault Date: Mon, 14 Jan 2019 07:31:24 -0800 Subject: [PATCH 11/21] Hyperlinker: avoid allocations and Strings Restructures the Hyperlinker parser so as to avoid allocations. The trick is the 'ByteString' and 'StringBuffer' have almost identical structures. In particular, it is possible to take 'ByteString' slice of a 'StringBuffer' without allocating anything. Instead of doing expensive processing on 'String's to split the CPP from the non-CPP and then passing the non-CPP to GHC, we move to a token-by-token approach, re-using the 'P' monad. This has one additional benefit too: failure to lex is only going to mess up the rest of the line instead of all the way to the next CPP. --- .../src/Haddock/Backends/Hyperlinker.hs | 14 +- .../Haddock/Backends/Hyperlinker/Parser.hs | 297 ++++++------------ .../Haddock/Backends/Hyperlinker/Renderer.hs | 30 +- .../src/Haddock/Backends/Hyperlinker/Types.hs | 6 +- haddock-api/src/Haddock/GhcUtils.hs | 129 +++++++- 5 files changed, 258 insertions(+), 218 deletions(-) diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker.hs b/haddock-api/src/Haddock/Backends/Hyperlinker.hs index 757ca66025..ff1a0da610 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker.hs @@ -11,7 +11,6 @@ import Haddock.Backends.Hyperlinker.Renderer import Haddock.Backends.Hyperlinker.Parser import Haddock.Backends.Hyperlinker.Types import Haddock.Backends.Hyperlinker.Utils -import Documentation.Haddock.Utf8 as Utf8 import Text.XHtml hiding (()) @@ -57,17 +56,18 @@ ppHyperlinkedModuleSource srcdir pretty srcs iface = case ifaceHieFile iface of (Just hfp) -> do u <- mkSplitUniqSupply 'a' - (hiefile,_) <- readHieFile (initNameCache u []) hfp + HieFile { hie_hs_file = file + , hie_asts = HieASTs asts + , hie_types = types + , hie_hs_src = rawSrc + } <- fmap fst (readHieFile (initNameCache u []) hfp) let mast = if M.size asts == 1 then snd <$> M.lookupMin asts else M.lookup (mkFastString file) asts - file = hie_hs_file hiefile - asts = getAsts $ hie_asts hiefile - tokens = parse df file (Utf8.decodeUtf8 $ hie_hs_src hiefile) + tokens = parse df file rawSrc case mast of Just ast -> - let types = hie_types hiefile - flatAst = fmap (\i -> recoverFullType i types) ast + let flatAst = fmap (\i -> recoverFullType i types) ast in writeUtf8File path . html . render' flatAst $ tokens Nothing | M.size asts == 0 -> return () diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs index 53bdc3bf48..bd00b32946 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs @@ -1,213 +1,119 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} module Haddock.Backends.Hyperlinker.Parser (parse) where -import Data.Either ( isRight, isLeft ) -import Data.List ( foldl', isPrefixOf, isSuffixOf ) -import Data.Maybe ( maybeToList ) -import Data.Char ( isSpace ) -import qualified Text.Read as R +import Control.Applicative ( Alternative(..) ) +import Data.List ( isPrefixOf, isSuffixOf ) -import GHC ( DynFlags, addSourceToTokens ) +import qualified Data.ByteString as BS + +import DynFlags ( DynFlags, gopt_set, gopt_unset, GeneralFlag(..) ) +import ErrUtils ( emptyMessages ) +import FastString ( mkFastString ) +import Lexer ( P(..), ParseResult(..), PState(..), Token(..) + , mkPState, lexer ) +import Outputable ( showSDoc, panic ) import SrcLoc -import FastString ( fsLit, mkFastString ) -import StringBuffer ( stringToStringBuffer ) -import Lexer ( Token(..) ) -import qualified Lexer as L +import StringBuffer ( StringBuffer, atEnd ) import Haddock.Backends.Hyperlinker.Types as T - +import Haddock.GhcUtils -- | Turn source code string into a stream of more descriptive tokens. -- -- Result should retain original file layout (including comments, whitespace, -- etc.), i.e. the following "law" should hold: -- --- prop> concat . map tkValue . parse = id +-- prop> BS.concat . map tkValue . parse dflags fpath = id -- -- (In reality, this only holds for input not containing '\r', '\t', '\f', '\v', -- characters, since GHC transforms those into ' ' and '\n') -parse :: DynFlags -> FilePath -> String -> [T.Token] -parse dflags fp = ghcToks . processCPP dflags fp . filterCRLF +parse :: DynFlags -> FilePath -> BS.ByteString -> [T.Token] +parse dflags fpath bs = case unP (go False []) initState of + POk _ toks -> reverse toks + PFailed _ ss errMsg -> panic $ "Hyperlinker parse error at " ++ show ss ++ + ": " ++ showSDoc dflags errMsg where - -- Remove CRLFs from source - filterCRLF :: String -> String - filterCRLF ('\r':'\n':cs) = '\n' : filterCRLF cs - filterCRLF (c:cs) = c : filterCRLF cs - filterCRLF [] = [] --- | Parse the source into tokens using the GHC lexer. --- --- * CPP lines are removed and reinserted as line-comments --- * top-level file pragmas are parsed as block comments (see the --- 'ITblockComment' case of 'classify' for more details) --- -processCPP :: DynFlags -- ^ GHC's flags - -> FilePath -- ^ source file name (for position information) - -> String -- ^ source file contents - -> [(Located L.Token, String)] -processCPP dflags fpath s = addSrc . go start . splitCPP $ s - where + buf = stringBufferFromByteString bs + dflags' = gopt_set (gopt_unset dflags Opt_Haddock) Opt_KeepRawTokenStream + initState = (Lexer.mkPState dflags' buf start) { use_pos_prags = False } start = mkRealSrcLoc (mkFastString fpath) 1 1 - addSrc = addSourceToTokens start (stringToStringBuffer s) - - -- Transform a list of Haskell/CPP lines into a list of tokens - go :: RealSrcLoc -> [Either String String] -> [Located L.Token] - go _ [] = [] - go pos ls = - let (hLinesRight, ls') = span isRight ls - (cppLinesLeft, rest) = span isLeft ls' - - hSrc = concat [ hLine | Right hLine <- hLinesRight ] - cppSrc = concat [ cppLine | Left cppLine <- cppLinesLeft ] - - in case L.lexTokenStream (stringToStringBuffer hSrc) pos dflags of - - -- Stuff that fails to lex gets turned into comments - L.PFailed _ _ss _msg -> - let (src_pos, failed) = mkToken ITunknown pos hSrc - (new_pos, cpp) = mkToken ITlineComment src_pos cppSrc - in failed : cpp : go new_pos rest - - -- Successfully lexed - L.POk ss toks -> - let (new_pos, cpp) = mkToken ITlineComment (L.loc ss) cppSrc - in toks ++ [cpp] ++ go new_pos rest - - -- Manually make a token from a 'String', advancing the cursor position - mkToken tok start' str = - let end = foldl' advanceSrcLoc start' str - in (end, L (RealSrcSpan $ mkRealSrcSpan start' end) (tok str)) - - --- | Split apart the initial file into Haskell source lines ('Left' entries) and --- CPP lines ('Right' entries). --- --- All characters in the input are present in the output: --- --- prop> concat . map (either id id) . splitCPP = id -splitCPP :: String -> [Either String String] -splitCPP "" = [] -splitCPP s | isCPPline s = Left l : splitCPP rest - | otherwise = Right l : splitCPP rest - where - ~(l, rest) = spanToNewline 0 s - - --- | Heuristic to decide if a line is going to be a CPP line. This should be a --- cheap operation since it is going to be run on every line being processed. --- --- Right now it just checks if the first non-whitespace character in the first --- five characters of the line is a '#': --- --- >>> isCPPline "#define FOO 1" --- True --- --- >>> isCPPline "\t\t #ifdef GHC" --- True --- --- >>> isCPPline " #endif" --- False --- -isCPPline :: String -> Bool -isCPPline = isPrefixOf "#" . dropWhile (`elem` " \t") . take 5 - - --- | Split a "line" off the front of a string, hopefully without cutting tokens --- in half. I say "hopefully" because knowing what a token is requires lexing, --- yet lexing depends on this function. --- --- All characters in the input are present in the output: --- --- prop> curry (++) . spanToNewLine 0 = id -spanToNewline :: Int -- ^ open '{-' - -> String -- ^ input - -> (String, String) - --- Base case and space characters -spanToNewline _ "" = ("", "") -spanToNewline n ('\n':str) | n <= 0 = ("\n", str) -spanToNewline n ('\n':str) | n <= 0 = ("\n", str) -spanToNewline n ('\\':'\n':str) = - let (str', rest) = spanToNewline n str - in ('\\':'\n':str', rest) - --- Block comments -spanToNewline n ('{':'-':str) = - let (str', rest) = spanToNewline (n+1) str - in ('{':'-':str', rest) -spanToNewline n ('-':'}':str) = - let (str', rest) = spanToNewline (n-1) str - in ('-':'}':str', rest) - --- When not in a block comment, try to lex a Haskell token -spanToNewline 0 str@(c:_) | ((lexed, str') : _) <- R.lex str, not (isSpace c) = - if all (== '-') lexed && length lexed >= 2 - -- A Haskell line comment - then case span (/= '\n') str' of - (str'', '\n':rest) -> (lexed ++ str'' ++ "\n", rest) - (_, _) -> (str, "") - - -- An actual Haskell token - else let (str'', rest) = spanToNewline 0 str' - in (lexed ++ str'', rest) - --- In all other cases, advance one character at a time -spanToNewline n (c:str) = - let (str', rest) = spanToNewline n str - in (c:str', rest) - - --- | Turn a list of GHC's 'L.Token' (and their source 'String') into a list of --- Haddock's 'T.Token'. -ghcToks :: [(Located L.Token, String)] -> [T.Token] -ghcToks = reverse . (\(_,ts,_) -> ts) . foldl' go (start, [], False) - where - start = mkRealSrcLoc (fsLit "lexing") 1 1 - - go :: (RealSrcLoc, [T.Token], Bool) - -- ^ current position, tokens accumulated, currently in pragma (or not) - - -> (Located L.Token, String) - -- ^ next token, its content - - -> (RealSrcLoc, [T.Token], Bool) - -- ^ new position, new tokens accumulated, currently in pragma (or not) - - go (pos, toks, in_prag) (L l tok, raw) = - ( next_pos - , classifiedTok ++ maybeToList white ++ toks - , inPragma in_prag tok - ) - where - (next_pos, white) = mkWhitespace pos l - - classifiedTok = [ Token (classify' tok) raw rss - | RealSrcSpan rss <- [l] - , not (null raw) - ] - - classify' | in_prag = const TkPragma - | otherwise = classify - - --- | Find the correct amount of whitespace between tokens. -mkWhitespace :: RealSrcLoc -> SrcSpan -> (RealSrcLoc, Maybe T.Token) -mkWhitespace prev spn = - case spn of - UnhelpfulSpan _ -> (prev,Nothing) - RealSrcSpan s | null wsstring -> (end, Nothing) - | otherwise -> (end, Just (Token TkSpace wsstring wsspan)) - where - start = realSrcSpanStart s - end = realSrcSpanEnd s - wsspan = mkRealSrcSpan prev start - nls = srcLocLine start - srcLocLine prev - spaces = if nls == 0 then srcLocCol start - srcLocCol prev - else srcLocCol start - 1 - wsstring = replicate nls '\n' ++ replicate spaces ' ' + go :: Bool -- ^ are we currently in a pragma? + -> [T.Token] -- ^ tokens accumulated so far (in reverse) + -> P [T.Token] + go inPrag toks = do + (b, _) <- getInput + if not (atEnd b) + then do + (newToks, inPrag') <- parseCppLine <|> parsePlainTok inPrag <|> unknownLine + go inPrag' (newToks ++ toks) + else + pure toks + + -- | Try to parse a CPP line (can fail) + parseCppLine :: P ([T.Token], Bool) + parseCppLine = do + (b, l) <- getInput + case tryCppLine l b of + Just (cppBStr, l', b') + -> let cppTok = T.Token { tkType = TkCpp + , tkValue = cppBStr + , tkSpan = mkRealSrcSpan l l' } + in setInput (b', l') *> pure ([cppTok], False) + _ -> empty + + -- | Try to parse a regular old token (can fail) + parsePlainTok :: Bool -> P ([T.Token], Bool) -- return list is only ever 0-2 elements + parsePlainTok inPrag = do + (bInit, lInit) <- getInput + L sp tok <- Lexer.lexer False return + (bEnd, _) <- getInput + case sp of + UnhelpfulSpan _ -> pure ([], False) -- pretend the token never existed + RealSrcSpan rsp -> pure $ + let typ = if inPrag then TkPragma else classify tok + RealSrcLoc lStart = srcSpanStart sp -- safe since @sp@ is real + (spaceBStr, bStart) = spanPosition lInit lStart bInit + tokBStr = splitStringBuffer bStart bEnd + plainTok = T.Token { tkType = typ + , tkValue = tokBStr + , tkSpan = rsp } + spaceTok = T.Token { tkType = TkSpace + , tkValue = spaceBStr + , tkSpan = mkRealSrcSpan lInit lStart } + inPrag' = inPragma inPrag tok + + in (plainTok : [ spaceTok | not (BS.null spaceBStr) ], inPrag') + + -- | Parse whatever remains of the line as an unknown token (can't fail) + unknownLine :: P ([T.Token], Bool) + unknownLine = do + (b, l) <- getInput + let (unkBStr, l', b') = spanLine l b + unkTok = T.Token { tkType = TkUnknown + , tkValue = unkBStr + , tkSpan = mkRealSrcSpan l l' } + setInput (b', l') + pure ([unkTok], False) + +-- | Get the input +getInput :: P (StringBuffer, RealSrcLoc) +getInput = P $ \p @ PState { buffer = buf, loc = srcLoc } -> POk p (buf, srcLoc) + +-- | Set the input +setInput :: (StringBuffer, RealSrcLoc) -> P () +setInput (buf, srcLoc) = P $ \p -> POk (p { buffer = buf, loc = srcLoc }) () + +-- | Orphan instance that adds backtracking to 'P' +instance Alternative P where + empty = P $ \_ -> PFailed (const emptyMessages) noSrcSpan "Alterative.empty" + P x <|> P y = P $ \s -> case x s of { p@POk{} -> p + ; _ -> y s } -- | Classify given tokens as appropriate Haskell token type. -classify :: L.Token -> TokenType +classify :: Lexer.Token -> TokenType classify tok = case tok of ITas -> TkKeyword @@ -382,12 +288,7 @@ classify tok = ITunknown {} -> TkUnknown ITeof -> TkUnknown - -- Line comments are only supposed to start with '--'. Starting with '#' - -- means that this was probably a CPP. - ITlineComment s - | isCPPline s -> TkCpp - | otherwise -> TkComment - + ITlineComment {} -> TkComment ITdocCommentNext {} -> TkComment ITdocCommentPrev {} -> TkComment ITdocCommentNamed {} -> TkComment @@ -404,9 +305,9 @@ classify tok = | otherwise -> TkComment -- | Classify given tokens as beginning pragmas (or not). -inPragma :: Bool -- ^ currently in pragma - -> L.Token -- ^ current token - -> Bool -- ^ new information about whether we are in a pragma +inPragma :: Bool -- ^ currently in pragma + -> Lexer.Token -- ^ current token + -> Bool -- ^ new information about whether we are in a pragma inPragma _ ITclose_prag = False inPragma True _ = True inPragma False tok = diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs index c7b2be0f7e..e2eedc5847 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs @@ -1,6 +1,7 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE OverloadedStrings #-} module Haddock.Backends.Hyperlinker.Renderer (render) where @@ -16,6 +17,7 @@ import Module ( ModuleName, moduleName, moduleNameString ) import Name ( getOccString, isInternalName, Name, nameModule, nameUnique ) import SrcLoc import Unique ( getKey ) +import Encoding ( utf8DecodeByteString ) import System.FilePath.Posix (()) @@ -90,14 +92,14 @@ renderWithAst df srcs ast toks = anchored $ case toks of | realSrcSpanStart s1 == realSrcSpanStart (nodeSpan ast) , realSrcSpanEnd s2 == realSrcSpanEnd (nodeSpan ast) -> richToken df srcs (nodeInfo ast) - (Token{ tkValue = "`" ++ tkValue tok ++ "`" + (Token{ tkValue = "`" <> tkValue tok <> "`" , tkType = TkOperator , tkSpan = nodeSpan ast }) [OpenParenTok s1, tok @ Token{ tkType = TkOperator }, CloseParenTok s2] | realSrcSpanStart s1 == realSrcSpanStart (nodeSpan ast) , realSrcSpanEnd s2 == realSrcSpanEnd (nodeSpan ast) -> richToken df srcs (nodeInfo ast) - (Token{ tkValue = "(" ++ tkValue tok ++ ")" + (Token{ tkValue = "(" <> tkValue tok <> ")" , tkType = TkOperator , tkSpan = nodeSpan ast }) @@ -115,20 +117,22 @@ renderWithAst df srcs ast toks = anchored $ case toks of renderToken :: Token -> Html renderToken Token{..} - | tkType == TkSpace = renderSpace (srcSpanStartLine tkSpan) tkValue - | otherwise = tokenSpan ! [ multiclass style ] - where - style = tokenStyle tkType - tokenSpan = Html.thespan (Html.toHtml tkValue) + | tkType == TkSpace = renderSpace (srcSpanStartLine tkSpan) tkValue' + | otherwise = tokenSpan ! [ multiclass style ] + where + tkValue' = filterCRLF $ utf8DecodeByteString tkValue + style = tokenStyle tkType + tokenSpan = Html.thespan (Html.toHtml tkValue') -- | Given information about the source position of definitions, render a token richToken :: DynFlags -> SrcMap -> NodeInfo HieTypeFix -> Token -> Html richToken df srcs details Token{..} - | tkType == TkSpace = renderSpace (srcSpanStartLine tkSpan) tkValue + | tkType == TkSpace = renderSpace (srcSpanStartLine tkSpan) tkValue' | otherwise = annotate df details $ linked content where + tkValue' = filterCRLF $ utf8DecodeByteString tkValue content = tokenSpan ! [ multiclass style ] - tokenSpan = Html.thespan (Html.toHtml tkValue) + tokenSpan = Html.thespan (Html.toHtml tkValue') style = tokenStyle tkType ++ concatMap richTokenStyle contexts contexts = concatMap (Set.elems . identInfo) . Map.elems . nodeIdentifiers $ details @@ -141,6 +145,12 @@ richToken df srcs details Token{..} Just (n,_) -> hyperlink srcs n Nothing -> id +-- | Remove CRLFs from source +filterCRLF :: String -> String +filterCRLF ('\r':'\n':cs) = '\n' : filterCRLF cs +filterCRLF (c:cs) = c : filterCRLF cs +filterCRLF [] = [] + annotate :: DynFlags -> NodeInfo HieTypeFix -> Html -> Html annotate df ni content = Html.thespan (annot <> content) ! [ Html.theclass "annot" ] @@ -254,7 +264,7 @@ externalModHyperlink srcs name content = renderSpace :: Int -> String -> Html renderSpace _ [] = Html.noHtml renderSpace line ('\n':rest) = mconcat - [ Html.thespan . Html.toHtml $ "\n" + [ Html.thespan (Html.toHtml '\n') , lineAnchor (line + 1) , renderSpace (line + 1) rest ] diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs index 72c896b4f4..5d8665ce0e 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs @@ -1,13 +1,15 @@ -{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE PatternSynonyms, OverloadedStrings #-} module Haddock.Backends.Hyperlinker.Types where import qualified GHC +import Data.ByteString ( ByteString ) + import Data.Map (Map) data Token = Token { tkType :: TokenType - , tkValue :: String + , tkValue :: ByteString -- ^ UTF-8 encoded , tkSpan :: {-# UNPACK #-} !Span } deriving (Show) diff --git a/haddock-api/src/Haddock/GhcUtils.hs b/haddock-api/src/Haddock/GhcUtils.hs index e9e06625f8..b4f20b7596 100644 --- a/haddock-api/src/Haddock/GhcUtils.hs +++ b/haddock-api/src/Haddock/GhcUtils.hs @@ -19,10 +19,12 @@ module Haddock.GhcUtils where import Control.Arrow +import Data.Char ( isSpace ) + import Haddock.Types( DocNameI ) import Exception -import Outputable +import Outputable ( Outputable, panic, showPpr ) import Name import NameSet import Module @@ -30,6 +32,14 @@ import HscTypes import GHC import Class import DynFlags +import SrcLoc ( advanceSrcLoc ) + +import StringBuffer ( StringBuffer ) +import qualified StringBuffer as S + +import Data.ByteString ( ByteString ) +import qualified Data.ByteString as BS +import qualified Data.ByteString.Internal as BS moduleString :: Module -> String @@ -422,3 +432,120 @@ setStubDir f d = d{ stubDir = Just f -- -stubdir D adds an implicit -I D, so that gcc can find the _stub.h file -- \#included from the .hc file when compiling with -fvia-C. setOutputDir f = setObjectDir f . setHiDir f . setHieDir f . setStubDir f + + +------------------------------------------------------------------------------- +-- * 'StringBuffer' and 'ByteString' +------------------------------------------------------------------------------- +-- We get away with a bunch of these functions because 'StringBuffer' and +-- 'ByteString' have almost exactly the same structure. + +-- | Convert a UTF-8 encoded 'ByteString' into a 'StringBuffer. This really +-- relies on the internals of both 'ByteString' and 'StringBuffer'. +-- +-- /O(n)/ (but optimized into a @memcpy@ by @bytestring@ under the hood) +stringBufferFromByteString :: ByteString -> StringBuffer +stringBufferFromByteString bs = + let BS.PS fp off len = bs <> BS.pack [0,0,0] + in S.StringBuffer { S.buf = fp, S.len = len - 3, S.cur = off } + +-- | Take the first @n@ /bytes/ of the 'StringBuffer' and put them in a +-- 'ByteString'. +-- +-- /O(1)/ +takeStringBuffer :: Int -> StringBuffer -> ByteString +takeStringBuffer !n !(S.StringBuffer fp _ cur) = BS.PS fp cur n + +-- | Return the prefix of the first 'StringBuffer' that /isn't/ in the second +-- 'StringBuffer'. **The behavior is undefined if the 'StringBuffers' use +-- separate buffers.** +-- +-- /O(1)/ +splitStringBuffer :: StringBuffer -> StringBuffer -> ByteString +splitStringBuffer buf1 buf2 = takeStringBuffer n buf1 + where n = S.byteDiff buf1 buf2 + +-- | Split the 'StringBuffer' at the next newline (or the end of the buffer). +-- Also: initial position is passed in and the updated position is returned. +-- +-- /O(n)/ (but /O(1)/ space) +spanLine :: RealSrcLoc -> StringBuffer -> (ByteString, RealSrcLoc, StringBuffer) +spanLine !loc !buf = go loc buf + where + + go !l !b + | not (S.atEnd b) + = case S.nextChar b of + ('\n', b') -> (splitStringBuffer buf b', advanceSrcLoc l '\n', b') + (c, b') -> go (advanceSrcLoc l c) b' + | otherwise + = (splitStringBuffer buf b, advanceSrcLoc l '\n', b) + +-- | Given a start position and a buffer with that start position, split the +-- buffer at an end position. +-- +-- /O(n)/ (but /O(1)/ space) +spanPosition :: RealSrcLoc -- ^ start of buffeer + -> RealSrcLoc -- ^ position until which to take + -> StringBuffer -- ^ buffer from which to take + -> (ByteString, StringBuffer) +spanPosition !start !end !buf = go start buf + where + + go !l !b + | l < end + , not (S.atEnd b) + , (c, b') <- S.nextChar b + = go (advanceSrcLoc l c) b' + | otherwise + = (splitStringBuffer buf b, b) + +-- | Try to parse a line of CPP from the from of the buffer. A \"line\" of CPP +-- consists of +-- +-- * at most 10 whitespace characters, including at least one newline +-- * a @#@ character +-- * keep parsing lines until you find a line not ending in @\\@. +-- +-- This is chock full of heuristics about what a line of CPP is. +-- +-- /O(n)/ (but /O(1)/ space) +tryCppLine :: RealSrcLoc -> StringBuffer -> Maybe (ByteString, RealSrcLoc, StringBuffer) +tryCppLine !loc !buf = spanSpace (S.prevChar buf '\n' == '\n') loc buf + where + + -- Keep consuming space characters until we hit either a @#@ or something + -- else. If we hit a @#@, start parsing CPP + spanSpace !seenNl !l !b + | S.atEnd b + = Nothing + | otherwise + = case S.nextChar b of + ('#' , b') | not (S.atEnd b') + , ('-', b'') <- S.nextChar b' + , ('}', _) <- S.nextChar b'' + -> Nothing -- Edge case exception for @#-}@ + | seenNl + -> Just (spanCppLine (advanceSrcLoc l '#') b') -- parse CPP + | otherwise + -> Nothing -- We didn't see a newline, so this can't be CPP! + + (c , b') | isSpace c -> spanSpace (seenNl || c == '\n') + (advanceSrcLoc l c) b' + | otherwise -> Nothing + + -- Consume a CPP line to its "end" (basically the first line that ends not + -- with a @\@ character) + spanCppLine !l !b + | S.atEnd b + = (splitStringBuffer buf b, l, b) + | otherwise + = case S.nextChar b of + ('\\', b') | not (S.atEnd b') + , ('\n', b'') <- S.nextChar b' + -> spanCppLine (advanceSrcLoc (advanceSrcLoc l '\\') '\n') b'' + + ('\n', b') -> (splitStringBuffer buf b', advanceSrcLoc l '\n', b') + + (c , b') -> spanCppLine (advanceSrcLoc l c) b' + From 5c244397cd5d28aed98ae09f8e6e759cdc8755e7 Mon Sep 17 00:00:00 2001 From: Alec Theriault Date: Wed, 16 Jan 2019 10:03:57 -0800 Subject: [PATCH 12/21] Hyperlinker: optimize some HIE transformations Optimized some of the batch HIE transformations to share intermediate results. This sort of thing probably belongs as a GHC-side utility. Also, avoid recomputing a module name based SrcMap. This should be a performance-only change. --- .../src/Haddock/Backends/Hyperlinker.hs | 75 +++++----- .../Haddock/Backends/Hyperlinker/Renderer.hs | 130 +++++++++--------- .../src/Haddock/Backends/Hyperlinker/Types.hs | 2 +- .../src/Haddock/Backends/Hyperlinker/Utils.hs | 79 ++++++++++- 4 files changed, 179 insertions(+), 107 deletions(-) diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker.hs b/haddock-api/src/Haddock/Backends/Hyperlinker.hs index ff1a0da610..251c886b0e 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE OverloadedStrings #-} module Haddock.Backends.Hyperlinker ( ppHyperlinkedSource , module Haddock.Backends.Hyperlinker.Types @@ -11,20 +12,19 @@ import Haddock.Backends.Hyperlinker.Renderer import Haddock.Backends.Hyperlinker.Parser import Haddock.Backends.Hyperlinker.Types import Haddock.Backends.Hyperlinker.Utils - -import Text.XHtml hiding (()) +import Haddock.Backends.Xhtml.Utils ( renderToString ) import Data.Maybe import System.Directory import System.FilePath -import HieTypes -import HieUtils (recoverFullType) -import HieBin +import HieTypes ( HieFile(..), HieASTs(..) ) +import HieBin ( readHieFile ) import Data.Map as M -import FastString -import NameCache -import UniqSupply +import FastString ( mkFastString ) +import Module ( Module, moduleName ) +import NameCache ( initNameCache ) +import UniqSupply ( mkSplitUniqSupply ) -- | Generate hyperlinked source for given interfaces. @@ -36,10 +36,10 @@ ppHyperlinkedSource :: FilePath -- ^ Output directory -> FilePath -- ^ Resource directory -> Maybe FilePath -- ^ Custom CSS file path -> Bool -- ^ Flag indicating whether to pretty-print HTML - -> SrcMap -- ^ Paths to sources + -> M.Map Module SrcPath -- ^ Paths to sources -> [Interface] -- ^ Interfaces for which we create source -> IO () -ppHyperlinkedSource outdir libdir mstyle pretty srcs ifaces = do +ppHyperlinkedSource outdir libdir mstyle pretty srcs' ifaces = do createDirectoryIfMissing True srcdir let cssFile = fromMaybe (defaultCssFile libdir) mstyle copyFile cssFile $ srcdir srcCssFile @@ -48,36 +48,38 @@ ppHyperlinkedSource outdir libdir mstyle pretty srcs ifaces = do mapM_ (ppHyperlinkedModuleSource srcdir pretty srcs) ifaces where srcdir = outdir hypSrcDir + srcs = (srcs', M.mapKeys moduleName srcs') -- | Generate hyperlinked source for particular interface. -ppHyperlinkedModuleSource :: FilePath -> Bool -> SrcMap -> Interface - -> IO () -ppHyperlinkedModuleSource srcdir pretty srcs iface = - case ifaceHieFile iface of - (Just hfp) -> do - u <- mkSplitUniqSupply 'a' - HieFile { hie_hs_file = file - , hie_asts = HieASTs asts - , hie_types = types - , hie_hs_src = rawSrc - } <- fmap fst (readHieFile (initNameCache u []) hfp) - let mast = if M.size asts == 1 - then snd <$> M.lookupMin asts - else M.lookup (mkFastString file) asts - tokens = parse df file rawSrc - case mast of - Just ast -> - let flatAst = fmap (\i -> recoverFullType i types) ast - in writeUtf8File path . html . render' flatAst $ tokens - Nothing - | M.size asts == 0 -> return () - | otherwise -> error $ unwords [ "couldn't find ast for" - , file, show (M.keys asts) ] - _ -> return () +ppHyperlinkedModuleSource :: FilePath -> Bool -> SrcMaps -> Interface -> IO () +ppHyperlinkedModuleSource srcdir pretty srcs iface = case ifaceHieFile iface of + Just hfp -> do + -- Parse the GHC-produced HIE file + u <- mkSplitUniqSupply 'a' + HieFile { hie_hs_file = file + , hie_asts = HieASTs asts + , hie_types = types + , hie_hs_src = rawSrc + } <- fmap fst (readHieFile (initNameCache u []) hfp) + + -- Get the AST and tokens corresponding to the source file we want + let mast | M.size asts == 1 = snd <$> M.lookupMin asts + | otherwise = M.lookup (mkFastString file) asts + tokens = parse df file rawSrc + + -- Produce and write out the hyperlinked sources + case mast of + Just ast -> + let fullAst = recoverFullIfaceTypes df types ast + in writeUtf8File path . renderToString pretty . render' fullAst $ tokens + Nothing + | M.size asts == 0 -> return () + | otherwise -> error $ unwords [ "couldn't find ast for" + , file, show (M.keys asts) ] + Nothing -> return () where df = ifaceDynFlags iface - render' = render (Just srcCssFile) (Just highlightScript) df srcs - html = if pretty then renderHtml else showHtml + render' = render (Just srcCssFile) (Just highlightScript) srcs path = srcdir hypSrcModuleFile (ifaceMod iface) -- | Name of CSS file in output directory. @@ -91,3 +93,4 @@ highlightScript = "highlight.js" -- | Path to default CSS file. defaultCssFile :: FilePath -> FilePath defaultCssFile libdir = libdir "html" "solarized.css" + diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs index e2eedc5847..f7a0e12605 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs @@ -9,11 +9,8 @@ module Haddock.Backends.Hyperlinker.Renderer (render) where import Haddock.Backends.Hyperlinker.Types import Haddock.Backends.Hyperlinker.Utils -import DynFlags ( DynFlags ) -import FastString (fsLit) import HieTypes -import HieUtils ( renderHieType ) -import Module ( ModuleName, moduleName, moduleNameString ) +import Module ( ModuleName, moduleNameString ) import Name ( getOccString, isInternalName, Name, nameModule, nameUnique ) import SrcLoc import Unique ( getKey ) @@ -34,17 +31,16 @@ type StyleClass = String render :: Maybe FilePath -- ^ path to the CSS file -> Maybe FilePath -- ^ path to the JS file - -> DynFlags -- ^ used to render types - -> SrcMap -- ^ Paths to sources - -> HieAST HieTypeFix -- ^ ASTs from @.hie@ files - -> [Token] -- ^ tokens to render + -> SrcMaps -- ^ Paths to sources + -> HieAST PrintedType -- ^ ASTs from @.hie@ files + -> [Token] -- ^ tokens to render -> Html -render mcss mjs df srcs ast tokens = header mcss mjs <> body df srcs ast tokens +render mcss mjs srcs ast tokens = header mcss mjs <> body srcs ast tokens -body :: DynFlags -> SrcMap -> HieAST HieTypeFix -> [Token] -> Html -body df srcs ast tokens = Html.body . Html.pre $ hypsrc +body :: SrcMaps -> HieAST PrintedType -> [Token] -> Html +body srcs ast tokens = Html.body . Html.pre $ hypsrc where - hypsrc = renderWithAst df srcs ast tokens + hypsrc = renderWithAst srcs ast tokens header :: Maybe FilePath -> Maybe FilePath -> Html header Nothing Nothing = Html.noHtml @@ -62,10 +58,10 @@ header mcss mjs = Html.header $ css mcss <> js mjs , Html.src scriptFile ] -splitTokens :: HieAST HieTypeFix -> [Token] -> ([Token],[Token],[Token]) -splitTokens ast toks' = (initial++before,during,after) + +splitTokens :: HieAST PrintedType -> [Token] -> ([Token],[Token],[Token]) +splitTokens ast toks = (before,during,after) where - (initial,toks) = span ((== fsLit "lexing") . srcSpanFile . tkSpan) toks' (before,rest) = span leftOf toks (during,after) = span inAst rest leftOf t = realSrcSpanEnd (tkSpan t) <= realSrcSpanStart nodeSp @@ -74,9 +70,10 @@ splitTokens ast toks' = (initial++before,during,after) -- | Turn a list of tokens into hyperlinked sources, threading in relevant link -- information from the 'HieAST'. -renderWithAst :: DynFlags -> SrcMap -> HieAST HieTypeFix -> [Token] -> Html -renderWithAst df srcs ast toks = anchored $ case toks of - [tok] | nodeSpan ast == tkSpan tok -> richToken df srcs (nodeInfo ast) tok +renderWithAst :: SrcMaps -> HieAST PrintedType -> [Token] -> Html +renderWithAst srcs Node{..} toks = anchored $ case toks of + + [tok] | nodeSpan == tkSpan tok -> richToken srcs nodeInfo tok -- NB: the GHC lexer lexes backquoted identifiers and parenthesized operators -- as multiple tokens. @@ -86,32 +83,32 @@ renderWithAst df srcs ast toks = anchored $ case toks of -- -- However, the HIE ast considers @`elem`@ and @(+)@ to be single nodes. In -- order to make sure these get hyperlinked properly, we intercept these - -- special sequences of tokens and turn merge then into just one identifier - -- or operator token. - [BacktickTok s1, tok @ Token{ tkType = TkIdentifier }, BacktickTok s2] - | realSrcSpanStart s1 == realSrcSpanStart (nodeSpan ast) - , realSrcSpanEnd s2 == realSrcSpanEnd (nodeSpan ast) - -> richToken df srcs (nodeInfo ast) + -- special sequences of tokens and merge them into just one identifier or + -- operator token. + [BacktickTok s1, tok @ Token{ tkType = TkIdentifier }, BacktickTok s2] + | realSrcSpanStart s1 == realSrcSpanStart nodeSpan + , realSrcSpanEnd s2 == realSrcSpanEnd nodeSpan + -> richToken srcs nodeInfo (Token{ tkValue = "`" <> tkValue tok <> "`" , tkType = TkOperator - , tkSpan = nodeSpan ast }) + , tkSpan = nodeSpan }) [OpenParenTok s1, tok @ Token{ tkType = TkOperator }, CloseParenTok s2] - | realSrcSpanStart s1 == realSrcSpanStart (nodeSpan ast) - , realSrcSpanEnd s2 == realSrcSpanEnd (nodeSpan ast) - -> richToken df srcs (nodeInfo ast) + | realSrcSpanStart s1 == realSrcSpanStart nodeSpan + , realSrcSpanEnd s2 == realSrcSpanEnd nodeSpan + -> richToken srcs nodeInfo (Token{ tkValue = "(" <> tkValue tok <> ")" , tkType = TkOperator - , tkSpan = nodeSpan ast }) + , tkSpan = nodeSpan }) - xs -> go (nodeChildren ast) xs + _ -> go nodeChildren toks where go _ [] = mempty go [] xs = foldMap renderToken xs go (cur:rest) xs = - foldMap renderToken before <> renderWithAst df srcs cur during <> go rest after + foldMap renderToken before <> renderWithAst srcs cur during <> go rest after where (before,during,after) = splitTokens cur xs - anchored c = Map.foldrWithKey anchorOne c (nodeIdentifiers $ nodeInfo ast) + anchored c = Map.foldrWithKey anchorOne c (nodeIdentifiers nodeInfo) anchorOne n dets c = externalAnchor n d $ internalAnchor n d c where d = identInfo dets @@ -124,11 +121,12 @@ renderToken Token{..} style = tokenStyle tkType tokenSpan = Html.thespan (Html.toHtml tkValue') + -- | Given information about the source position of definitions, render a token -richToken :: DynFlags -> SrcMap -> NodeInfo HieTypeFix -> Token -> Html -richToken df srcs details Token{..} +richToken :: SrcMaps -> NodeInfo PrintedType -> Token -> Html +richToken srcs details Token{..} | tkType == TkSpace = renderSpace (srcSpanStartLine tkSpan) tkValue' - | otherwise = annotate df details $ linked content + | otherwise = annotate details $ linked content where tkValue' = filterCRLF $ utf8DecodeByteString tkValue content = tokenSpan ! [ multiclass style ] @@ -151,8 +149,8 @@ filterCRLF ('\r':'\n':cs) = '\n' : filterCRLF cs filterCRLF (c:cs) = c : filterCRLF cs filterCRLF [] = [] -annotate :: DynFlags -> NodeInfo HieTypeFix -> Html -> Html -annotate df ni content = +annotate :: NodeInfo PrintedType -> Html -> Html +annotate ni content = Html.thespan (annot <> content) ! [ Html.theclass "annot" ] where annot @@ -160,11 +158,11 @@ annotate df ni content = Html.thespan (Html.toHtml annotation) ! [ Html.theclass "annottext" ] | otherwise = mempty annotation = typ ++ identTyps - typ = unlines $ map (renderHieType df) $ nodeType ni + typ = unlines (nodeType ni) typedIdents = [ (n,t) | (n, identType -> Just t) <- Map.toList $ nodeIdentifiers ni ] identTyps - | length typedIdents > 1 || null typ - = concatMap (\(n,t) -> printName n ++ " :: " ++ renderHieType df t ++ "\n") typedIdents + | length typedIdents > 1 || null (nodeType ni) + = concatMap (\(n,t) -> printName n ++ " :: " ++ t ++ "\n") typedIdents | otherwise = "" printName :: Either ModuleName Name -> String @@ -230,35 +228,33 @@ externalAnchorIdent = hypSrcNameUrl internalAnchorIdent :: Name -> String internalAnchorIdent = ("local-" ++) . show . getKey . nameUnique -hyperlink :: SrcMap -> Identifier -> Html -> Html -hyperlink srcs ident = case ident of +-- | Generate the HTML hyperlink for an identifier +hyperlink :: SrcMaps -> Identifier -> Html -> Html +hyperlink (srcs, srcs') ident = case ident of Right name | isInternalName name -> internalHyperlink name - | otherwise -> externalNameHyperlink srcs name - Left name -> externalModHyperlink srcs name - -internalHyperlink :: Name -> Html -> Html -internalHyperlink name content = - Html.anchor content ! [ Html.href $ "#" ++ internalAnchorIdent name ] - -externalNameHyperlink :: SrcMap -> Name -> Html -> Html -externalNameHyperlink srcs name content = case Map.lookup mdl srcs of - Just SrcLocal -> Html.anchor content ! - [ Html.href $ hypSrcModuleNameUrl mdl name ] - Just (SrcExternal path) -> Html.anchor content ! - [ Html.href $ spliceURL Nothing (Just mdl) (Just name) Nothing (".." path) ] - Nothing -> content + | otherwise -> externalNameHyperlink name + Left name -> externalModHyperlink name + where - mdl = nameModule name - -externalModHyperlink :: SrcMap -> ModuleName -> Html -> Html -externalModHyperlink srcs name content = - let srcs' = Map.mapKeys moduleName srcs in - case Map.lookup name srcs' of - Just SrcLocal -> Html.anchor content ! - [ Html.href $ hypSrcModuleUrl' name ] - Just (SrcExternal path) -> Html.anchor content ! - [ Html.href $ path hypSrcModuleUrl' name ] - Nothing -> content + internalHyperlink name content = + Html.anchor content ! [ Html.href $ "#" ++ internalAnchorIdent name ] + + externalNameHyperlink name content = case Map.lookup mdl srcs of + Just SrcLocal -> Html.anchor content ! + [ Html.href $ hypSrcModuleNameUrl mdl name ] + Just (SrcExternal path) -> Html.anchor content ! + [ Html.href $ spliceURL Nothing (Just mdl) (Just name) Nothing (".." path) ] + Nothing -> content + where + mdl = nameModule name + + externalModHyperlink name content = + case Map.lookup name srcs' of + Just SrcLocal -> Html.anchor content ! + [ Html.href $ hypSrcModuleUrl' name ] + Just (SrcExternal path) -> Html.anchor content ! + [ Html.href $ path hypSrcModuleUrl' name ] + Nothing -> content renderSpace :: Int -> String -> Html diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs index 5d8665ce0e..50916937ef 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Types.hs @@ -47,5 +47,5 @@ data SrcPath | SrcLocal -- | Mapping from modules to cross-package source paths. -type SrcMap = Map GHC.Module SrcPath +type SrcMaps = (Map GHC.Module SrcPath, Map GHC.ModuleName SrcPath) diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs index 20b0adedd3..10693f7745 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE OverloadedStrings #-} module Haddock.Backends.Hyperlinker.Utils ( hypSrcDir, hypSrcModuleFile, hypSrcModuleFile' , hypSrcModuleUrl, hypSrcModuleUrl' @@ -7,16 +8,26 @@ module Haddock.Backends.Hyperlinker.Utils , hypSrcModuleUrlFormat , hypSrcModuleNameUrlFormat, hypSrcModuleLineUrlFormat , spliceURL - ) where + -- * HIE file processing + , PrintedType + , recoverFullIfaceTypes + ) where import Haddock.Backends.Xhtml.Utils +import FastString ( nilFS ) import GHC -import FastString -import Name (nameModule_maybe) +import HieTypes ( HieAST(..), HieType(..), HieArgs(..), TypeIndex, HieTypeFlat ) +import IfaceType +import Name ( getOccFS, nameModule_maybe ) +import Outputable ( showSDoc ) +import Var ( VarBndr(..) ) + import System.FilePath.Posix (()) +import qualified Data.Array as A + hypSrcDir :: FilePath hypSrcDir = "src" @@ -68,3 +79,65 @@ nameFormat = "%{NAME}" lineFormat :: String lineFormat = "line-%{LINE}" + + +-- * HIE file procesddsing + +-- This belongs in GHC's HieUtils... + +-- | Pretty-printed type, ready to be turned into HTML by @xhtml@ +type PrintedType = String + +-- | Expand the flattened HIE AST into one where the types printed out and +-- ready for end-users to look at. +-- +-- Using just primitives found in GHC's HIE utilities, we could write this as +-- follows: +-- +-- > 'recoverFullIfaceTypes' dflags hieTypes hieAst +-- > = 'fmap' (\ti -> 'showSDoc' df . +-- > 'pprIfaceType' $ +-- > 'recoverFullType' ti hieTypes) +-- > hieAst +-- +-- However, this is very inefficient (both in time and space) because the +-- mutliple calls to 'recoverFullType' don't share intermediate results. This +-- function fixes that. +recoverFullIfaceTypes + :: DynFlags + -> A.Array TypeIndex HieTypeFlat -- ^ flat types + -> HieAST TypeIndex -- ^ flattened AST + -> HieAST PrintedType -- ^ full AST +recoverFullIfaceTypes df flattened ast = fmap (printed A.!) ast + where + + -- Splitting this out into its own array is also important: we don't want + -- to pretty print the same type many times + printed :: A.Array TypeIndex PrintedType + printed = fmap (showSDoc df . pprIfaceType) unflattened + + -- The recursion in 'unflattened' is crucial - it's what gives us sharing + -- between the IfaceType's produced + unflattened :: A.Array TypeIndex IfaceType + unflattened = fmap (\flatTy -> go (fmap (unflattened A.!) flatTy)) flattened + + -- Unfold an 'HieType' whose subterms have already been unfolded + go :: HieType IfaceType -> IfaceType + go (HTyVarTy n) = IfaceTyVar (getOccFS n) + go (HAppTy a b) = IfaceAppTy a (hieToIfaceArgs b) + go (HLitTy l) = IfaceLitTy l + go (HForAllTy ((n,k),af) t) = let b = (getOccFS n, k) + in IfaceForAllTy (Bndr (IfaceTvBndr b) af) t + go (HFunTy a b) = IfaceFunTy a b + go (HQualTy con b) = IfaceDFunTy con b + go (HCastTy a) = a + go HCoercionTy = IfaceTyVar "" + go (HTyConApp a xs) = IfaceTyConApp a (hieToIfaceArgs xs) + + -- This isn't fully faithful - we can't produce the 'Inferred' case + hieToIfaceArgs :: HieArgs IfaceType -> IfaceAppArgs + hieToIfaceArgs (HieArgs args) = go' args + where + go' [] = IA_Nil + go' ((True ,x):xs) = IA_Arg x Required $ go' xs + go' ((False,x):xs) = IA_Arg x Specified $ go' xs From 828bcc82132cc85e74b60081bb3f502d6db5ff36 Mon Sep 17 00:00:00 2001 From: Alec Theriault Date: Wed, 16 Jan 2019 20:15:04 -0800 Subject: [PATCH 13/21] Hyperlinker: fix some malformed module links Also avoid an unnecessary call to splice URL in those cases. --- haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs | 8 ++++---- haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs index f7a0e12605..b6eb863018 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs @@ -248,12 +248,12 @@ hyperlink (srcs, srcs') ident = case ident of where mdl = nameModule name - externalModHyperlink name content = - case Map.lookup name srcs' of + externalModHyperlink moduleName content = + case Map.lookup moduleName srcs' of Just SrcLocal -> Html.anchor content ! - [ Html.href $ hypSrcModuleUrl' name ] + [ Html.href $ hypSrcModuleUrl' moduleName ] Just (SrcExternal path) -> Html.anchor content ! - [ Html.href $ path hypSrcModuleUrl' name ] + [ Html.href $ spliceURL' Nothing (Just moduleName) Nothing Nothing (".." path) ] Nothing -> content diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs index 10693f7745..1c2d17588f 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs @@ -7,7 +7,7 @@ module Haddock.Backends.Hyperlinker.Utils , hypSrcModuleNameUrl, hypSrcModuleLineUrl , hypSrcModuleUrlFormat , hypSrcModuleNameUrlFormat, hypSrcModuleLineUrlFormat - , spliceURL + , spliceURL, spliceURL' -- * HIE file processing , PrintedType @@ -24,7 +24,7 @@ import Name ( getOccFS, nameModule_maybe ) import Outputable ( showSDoc ) import Var ( VarBndr(..) ) -import System.FilePath.Posix (()) +import System.FilePath.Posix ((), (<.>)) import qualified Data.Array as A @@ -33,7 +33,7 @@ hypSrcDir :: FilePath hypSrcDir = "src" hypSrcModuleFile :: Module -> FilePath -hypSrcModuleFile = hypSrcModuleFile' . moduleName +hypSrcModuleFile m = moduleNameString (moduleName m) <.> "html" hypSrcModuleFile' :: ModuleName -> FilePath hypSrcModuleFile' mdl = spliceURL' @@ -47,7 +47,7 @@ hypSrcModuleUrl' = hypSrcModuleFile' hypSrcNameUrl :: Name -> String hypSrcNameUrl name = spliceURL - Nothing (nameModule_maybe name) (Just name) Nothing nameFormat + Nothing Nothing (Just name) Nothing nameFormat hypSrcLineUrl :: Int -> String hypSrcLineUrl line = spliceURL From c8726236845a6311420e84555164ad51886f69db Mon Sep 17 00:00:00 2001 From: Alec Theriault Date: Sat, 19 Jan 2019 11:54:54 -0800 Subject: [PATCH 14/21] Match GHC changes to parser API --- .../src/Haddock/Backends/Hyperlinker/Parser.hs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs index bd00b32946..d632da002d 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs @@ -7,11 +7,11 @@ import Data.List ( isPrefixOf, isSuffixOf ) import qualified Data.ByteString as BS -import DynFlags ( DynFlags, gopt_set, gopt_unset, GeneralFlag(..) ) +import DynFlags ( DynFlags, warningFlags, extensionFlags, thisPackage, safeImportsOn ) import ErrUtils ( emptyMessages ) import FastString ( mkFastString ) import Lexer ( P(..), ParseResult(..), PState(..), Token(..) - , mkPState, lexer ) + , mkPStatePure, lexer, mkParserFlags' ) import Outputable ( showSDoc, panic ) import SrcLoc import StringBuffer ( StringBuffer, atEnd ) @@ -35,10 +35,16 @@ parse dflags fpath bs = case unP (go False []) initState of ": " ++ showSDoc dflags errMsg where + initState = mkPStatePure pflags buf start buf = stringBufferFromByteString bs - dflags' = gopt_set (gopt_unset dflags Opt_Haddock) Opt_KeepRawTokenStream - initState = (Lexer.mkPState dflags' buf start) { use_pos_prags = False } start = mkRealSrcLoc (mkFastString fpath) 1 1 + pflags = mkParserFlags' (warningFlags dflags) + (extensionFlags dflags) + (thisPackage dflags) + (safeImportsOn dflags) + False -- lex Haddocks as comment tokens + True -- produce comment tokens + True -- produce position pragmas tokens go :: Bool -- ^ are we currently in a pragma? -> [T.Token] -- ^ tokens accumulated so far (in reverse) From d0bf8d8b451747f7e66e76f84e0d2f1195284332 Mon Sep 17 00:00:00 2001 From: Alec Theriault Date: Thu, 24 Jan 2019 13:02:03 -0800 Subject: [PATCH 15/21] Hyperlinker: fix Clang-specific off-by-one This is yet another example of why we need to stop using CPP. Clang's CPP inserts a newline after lines that _look_ like (but don't end up actually being) preprocessor directives. See the comment on `needPragHack` for more on this sad state of affairs. --- haddock-api/haddock-api.cabal | 2 + .../src/Haddock/Backends/Hyperlinker.hs | 5 +- .../Haddock/Backends/Hyperlinker/Parser.hs | 70 +++++++++++++++---- haddock.cabal | 1 + hypsrc-test/src/ClangCppBug.hs | 21 ++++++ 5 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 hypsrc-test/src/ClangCppBug.hs diff --git a/haddock-api/haddock-api.cabal b/haddock-api/haddock-api.cabal index 7e8f38800e..a4dea01ff6 100644 --- a/haddock-api/haddock-api.cabal +++ b/haddock-api/haddock-api.cabal @@ -59,6 +59,7 @@ library , directory , filepath , ghc-boot + , ghc-boot-th , transformers hs-source-dirs: src @@ -185,6 +186,7 @@ test-suite spec , directory , filepath , ghc-boot + , ghc-boot-th , transformers build-tool-depends: diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker.hs b/haddock-api/src/Haddock/Backends/Hyperlinker.hs index 251c886b0e..e63b591b19 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker.hs @@ -15,6 +15,7 @@ import Haddock.Backends.Hyperlinker.Utils import Haddock.Backends.Xhtml.Utils ( renderToString ) import Data.Maybe +import Data.IORef ( readIORef ) import System.Directory import System.FilePath @@ -25,6 +26,7 @@ import FastString ( mkFastString ) import Module ( Module, moduleName ) import NameCache ( initNameCache ) import UniqSupply ( mkSplitUniqSupply ) +import SysTools.Info ( getCompilerInfo' ) -- | Generate hyperlinked source for given interfaces. @@ -61,11 +63,12 @@ ppHyperlinkedModuleSource srcdir pretty srcs iface = case ifaceHieFile iface of , hie_types = types , hie_hs_src = rawSrc } <- fmap fst (readHieFile (initNameCache u []) hfp) + comp <- getCompilerInfo' df -- Get the AST and tokens corresponding to the source file we want let mast | M.size asts == 1 = snd <$> M.lookupMin asts | otherwise = M.lookup (mkFastString file) asts - tokens = parse df file rawSrc + tokens = parse comp df file rawSrc -- Produce and write out the hyperlinked sources case mast of diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs index d632da002d..b060c8f06f 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs @@ -6,8 +6,12 @@ import Control.Applicative ( Alternative(..) ) import Data.List ( isPrefixOf, isSuffixOf ) import qualified Data.ByteString as BS +import qualified Data.ByteString.Char8 as BSC -import DynFlags ( DynFlags, warningFlags, extensionFlags, thisPackage, safeImportsOn ) +import GHC.LanguageExtensions.Type + +import DynFlags +import qualified EnumSet as E import ErrUtils ( emptyMessages ) import FastString ( mkFastString ) import Lexer ( P(..), ParseResult(..), PState(..), Token(..) @@ -21,15 +25,15 @@ import Haddock.GhcUtils -- | Turn source code string into a stream of more descriptive tokens. -- --- Result should retain original file layout (including comments, whitespace, --- etc.), i.e. the following "law" should hold: --- --- prop> BS.concat . map tkValue . parse dflags fpath = id --- --- (In reality, this only holds for input not containing '\r', '\t', '\f', '\v', --- characters, since GHC transforms those into ' ' and '\n') -parse :: DynFlags -> FilePath -> BS.ByteString -> [T.Token] -parse dflags fpath bs = case unP (go False []) initState of +-- Result should retain original file layout (including comments, +-- whitespace, and CPP). +parse + :: CompilerInfo -- ^ Underlying CC compiler (whatever expanded CPP) + -> DynFlags -- ^ Flags for this module + -> FilePath -- ^ Path to the source of this module + -> BS.ByteString -- ^ Raw UTF-8 encoded source of this module + -> [T.Token] +parse comp dflags fpath bs = case unP (go False []) initState of POk _ toks -> reverse toks PFailed _ ss errMsg -> panic $ "Hyperlinker parse error at " ++ show ss ++ ": " ++ showSDoc dflags errMsg @@ -38,6 +42,7 @@ parse dflags fpath bs = case unP (go False []) initState of initState = mkPStatePure pflags buf start buf = stringBufferFromByteString bs start = mkRealSrcLoc (mkFastString fpath) 1 1 + needPragHack' = needPragHack comp dflags pflags = mkParserFlags' (warningFlags dflags) (extensionFlags dflags) (thisPackage dflags) @@ -78,7 +83,7 @@ parse dflags fpath bs = case unP (go False []) initState of (bEnd, _) <- getInput case sp of UnhelpfulSpan _ -> pure ([], False) -- pretend the token never existed - RealSrcSpan rsp -> pure $ + RealSrcSpan rsp -> do let typ = if inPrag then TkPragma else classify tok RealSrcLoc lStart = srcSpanStart sp -- safe since @sp@ is real (spaceBStr, bStart) = spanPosition lInit lStart bInit @@ -91,7 +96,15 @@ parse dflags fpath bs = case unP (go False []) initState of , tkSpan = mkRealSrcSpan lInit lStart } inPrag' = inPragma inPrag tok - in (plainTok : [ spaceTok | not (BS.null spaceBStr) ], inPrag') + -- See 'needPragHack' + case tok of + ITclose_prag{} + | needPragHack' + , '\n' `BSC.elem` spaceBStr + -> getInput >>= \(b,p) -> setInput (b,advanceSrcLoc p '\n') + _ -> pure () + + pure (plainTok : [ spaceTok | not (BS.null spaceBStr) ], inPrag') -- | Parse whatever remains of the line as an unknown token (can't fail) unknownLine :: P ([T.Token], Bool) @@ -104,6 +117,38 @@ parse dflags fpath bs = case unP (go False []) initState of setInput (b', l') pure ([unkTok], False) + +-- | This is really, really, /really/ gross. Problem: consider a Haskell +-- file that looks like: +-- +-- @ +-- {-# LANGUAGE CPP #-} +-- module SomeMod where +-- +-- #define SIX 6 +-- +-- {-# INLINE foo +-- #-} +-- foo = 1 +-- @ +-- +-- Clang's CPP replaces the @#define SIX 6@ line with an empty line (as it +-- should), but get confused about @#-}@. I'm guessing it /starts/ by +-- parsing that as a pre-processor directive and, when it fails to, it just +-- leaves the line alone. HOWEVER, it still adds an extra newline. =.= +-- +-- This function makes sure that the Hyperlinker backend also adds that +-- extra newline (or else our spans won't line up with GHC's anymore). +needPragHack :: CompilerInfo -> DynFlags -> Bool +needPragHack comp dflags = isCcClang && E.member Cpp (extensionFlags dflags) + where + isCcClang = case comp of + GCC -> False + Clang -> True + AppleClang -> True + AppleClang51 -> True + UnknownCC -> False + -- | Get the input getInput :: P (StringBuffer, RealSrcLoc) getInput = P $ \p @ PState { buffer = buf, loc = srcLoc } -> POk p (buf, srcLoc) @@ -112,6 +157,7 @@ getInput = P $ \p @ PState { buffer = buf, loc = srcLoc } -> POk p (buf, srcLoc) setInput :: (StringBuffer, RealSrcLoc) -> P () setInput (buf, srcLoc) = P $ \p -> POk (p { buffer = buf, loc = srcLoc }) () + -- | Orphan instance that adds backtracking to 'P' instance Alternative P where empty = P $ \_ -> PFailed (const emptyMessages) noSrcSpan "Alterative.empty" diff --git a/haddock.cabal b/haddock.cabal index 64a58d0a0f..e1f7565a47 100644 --- a/haddock.cabal +++ b/haddock.cabal @@ -78,6 +78,7 @@ executable haddock xhtml >= 3000.2 && < 3000.3, Cabal >= 1.10, ghc-boot, + ghc-boot-th, ghc == 8.7.*, bytestring, parsec, diff --git a/hypsrc-test/src/ClangCppBug.hs b/hypsrc-test/src/ClangCppBug.hs new file mode 100644 index 0000000000..4b0bc35f6f --- /dev/null +++ b/hypsrc-test/src/ClangCppBug.hs @@ -0,0 +1,21 @@ +{-# LANGUAGE CPP #-} +module ClangCppBug where + +foo :: Int +foo = 1 + +-- Clang doesn't mind these: +#define BAX 2 +{-# INLINE bar #-} + +bar :: Int +bar = 3 + +-- But it doesn't like this: +{-# RULES +"bar/qux" bar = qux +"qux/foo" qux = foo + #-} + +qux :: Int +qux = 88 From 43210a152ad8d254fc5cf00c057fbbed481212ce Mon Sep 17 00:00:00 2001 From: Alec Theriault Date: Mon, 28 Jan 2019 15:09:29 -0800 Subject: [PATCH 16/21] Handle 'LINE' and 'COLUMN' pragmas This is twofold: 1. ask GHC to treat these as regular tokens 2. we manually update the internal position as well as returning tokens If we don't do 1, we end up with huge chunks of text that get considered as whitespace (because a `LINE`/`COLUMN` pragma caused us to jump to that next place). If we do 1 but not 2, the positions we associate with tokens won't match those from HIE files, and we consequently won't get any links/type annotations. --- .../Haddock/Backends/Hyperlinker/Parser.hs | 53 +++++++++++++++---- haddock-api/src/Haddock/GhcUtils.hs | 2 +- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs index b060c8f06f..3aa4dfb58a 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs @@ -10,6 +10,7 @@ import qualified Data.ByteString.Char8 as BSC import GHC.LanguageExtensions.Type +import BasicTypes ( SourceText(..), IntegralLit(..) ) import DynFlags import qualified EnumSet as E import ErrUtils ( emptyMessages ) @@ -49,7 +50,7 @@ parse comp dflags fpath bs = case unP (go False []) initState of (safeImportsOn dflags) False -- lex Haddocks as comment tokens True -- produce comment tokens - True -- produce position pragmas tokens + False -- produce position pragmas tokens go :: Bool -- ^ are we currently in a pragma? -> [T.Token] -- ^ tokens accumulated so far (in reverse) @@ -87,22 +88,48 @@ parse comp dflags fpath bs = case unP (go False []) initState of let typ = if inPrag then TkPragma else classify tok RealSrcLoc lStart = srcSpanStart sp -- safe since @sp@ is real (spaceBStr, bStart) = spanPosition lInit lStart bInit - tokBStr = splitStringBuffer bStart bEnd + inPragDef = inPragma inPrag tok + + (bEnd', inPrag') <- case tok of + + -- Update internal line + file position if this is a LINE pragma + ITline_prag _ -> tryOrElse (bEnd, inPragDef) $ do + L _ (ITinteger (IL { il_value = line })) <- Lexer.lexer False return + L _ (ITstring _ file) <- Lexer.lexer False return + L (RealSrcSpan spF) ITclose_prag <- Lexer.lexer False return + + let newLoc = mkRealSrcLoc file (fromIntegral line - 1) (srcSpanEndCol spF) + (bEnd'', _) <- getInput + setInput (bEnd'', newLoc) + + pure (bEnd'', False) + + -- Update internal column position if this is a COLUMN pragma + ITcolumn_prag _ -> tryOrElse (bEnd, inPragDef) $ do + L _ (ITinteger (IL { il_value = col })) <- Lexer.lexer False return + L (RealSrcSpan spF) ITclose_prag <- Lexer.lexer False return + + let newLoc = mkRealSrcLoc (srcSpanFile spF) (srcSpanEndLine spF) (fromIntegral col) + (bEnd'', _) <- getInput + setInput (bEnd'', newLoc) + + pure (bEnd'', False) + + -- See 'needPragHack' + ITclose_prag{} + | needPragHack' + , '\n' `BSC.elem` spaceBStr + -> getInput >>= \(b,p) -> setInput (b,advanceSrcLoc p '\n') >> pure (bEnd, False) + + _ -> pure (bEnd, inPragDef) + + let tokBStr = splitStringBuffer bStart bEnd' plainTok = T.Token { tkType = typ , tkValue = tokBStr , tkSpan = rsp } spaceTok = T.Token { tkType = TkSpace , tkValue = spaceBStr , tkSpan = mkRealSrcSpan lInit lStart } - inPrag' = inPragma inPrag tok - - -- See 'needPragHack' - case tok of - ITclose_prag{} - | needPragHack' - , '\n' `BSC.elem` spaceBStr - -> getInput >>= \(b,p) -> setInput (b,advanceSrcLoc p '\n') - _ -> pure () pure (plainTok : [ spaceTok | not (BS.null spaceBStr) ], inPrag') @@ -164,6 +191,10 @@ instance Alternative P where P x <|> P y = P $ \s -> case x s of { p@POk{} -> p ; _ -> y s } +-- | Try a parser. If it fails, backtrack and return the pure value. +tryOrElse :: a -> P a -> P a +tryOrElse x p = p <|> pure x + -- | Classify given tokens as appropriate Haskell token type. classify :: Lexer.Token -> TokenType classify tok = diff --git a/haddock-api/src/Haddock/GhcUtils.hs b/haddock-api/src/Haddock/GhcUtils.hs index b4f20b7596..a342de0068 100644 --- a/haddock-api/src/Haddock/GhcUtils.hs +++ b/haddock-api/src/Haddock/GhcUtils.hs @@ -511,7 +511,7 @@ spanPosition !start !end !buf = go start buf -- -- /O(n)/ (but /O(1)/ space) tryCppLine :: RealSrcLoc -> StringBuffer -> Maybe (ByteString, RealSrcLoc, StringBuffer) -tryCppLine !loc !buf = spanSpace (S.prevChar buf '\n' == '\n') loc buf +tryCppLine !loc !buf = spanSpace (S.prevChar buf '\n' == '\n') loc buf where -- Keep consuming space characters until we hit either a @#@ or something From ce21874f23266eaa5b8adecb12d919b971102276 Mon Sep 17 00:00:00 2001 From: Alec Theriault Date: Wed, 30 Jan 2019 11:29:11 -0800 Subject: [PATCH 17/21] Ignore 0 length tokens Fixes some annoyances around LINE/COLUMN pragmas not being recognized properly due to the lexer inserting extra zero length tokens. Also, avoid rendering a zero length token. --- .../src/Haddock/Backends/Hyperlinker.hs | 1 - .../Haddock/Backends/Hyperlinker/Parser.hs | 22 ++++++++++++++----- .../Haddock/Backends/Hyperlinker/Renderer.hs | 3 +++ hypsrc-test/src/PositionPragmas.hs | 12 ++++++++++ 4 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 hypsrc-test/src/PositionPragmas.hs diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker.hs b/haddock-api/src/Haddock/Backends/Hyperlinker.hs index e63b591b19..5ef7d9bba6 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker.hs @@ -15,7 +15,6 @@ import Haddock.Backends.Hyperlinker.Utils import Haddock.Backends.Xhtml.Utils ( renderToString ) import Data.Maybe -import Data.IORef ( readIORef ) import System.Directory import System.FilePath diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs index 3aa4dfb58a..1d5576cce9 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Parser.hs @@ -10,7 +10,7 @@ import qualified Data.ByteString.Char8 as BSC import GHC.LanguageExtensions.Type -import BasicTypes ( SourceText(..), IntegralLit(..) ) +import BasicTypes ( IntegralLit(..) ) import DynFlags import qualified EnumSet as E import ErrUtils ( emptyMessages ) @@ -64,6 +64,16 @@ parse comp dflags fpath bs = case unP (go False []) initState of else pure toks + -- | Like 'Lexer.lexer', but slower, with a better API, and filtering out empty tokens + wrappedLexer :: P (RealLocated Lexer.Token) + wrappedLexer = Lexer.lexer False andThen + where andThen (L (RealSrcSpan s) t) + | srcSpanStartLine s /= srcSpanEndLine s || + srcSpanStartCol s /= srcSpanEndCol s + = pure (L s t) + andThen (L (RealSrcSpan s) ITeof) = pure (L s ITeof) + andThen _ = wrappedLexer + -- | Try to parse a CPP line (can fail) parseCppLine :: P ([T.Token], Bool) parseCppLine = do @@ -94,9 +104,9 @@ parse comp dflags fpath bs = case unP (go False []) initState of -- Update internal line + file position if this is a LINE pragma ITline_prag _ -> tryOrElse (bEnd, inPragDef) $ do - L _ (ITinteger (IL { il_value = line })) <- Lexer.lexer False return - L _ (ITstring _ file) <- Lexer.lexer False return - L (RealSrcSpan spF) ITclose_prag <- Lexer.lexer False return + L _ (ITinteger (IL { il_value = line })) <- wrappedLexer + L _ (ITstring _ file) <- wrappedLexer + L spF ITclose_prag <- wrappedLexer let newLoc = mkRealSrcLoc file (fromIntegral line - 1) (srcSpanEndCol spF) (bEnd'', _) <- getInput @@ -106,8 +116,8 @@ parse comp dflags fpath bs = case unP (go False []) initState of -- Update internal column position if this is a COLUMN pragma ITcolumn_prag _ -> tryOrElse (bEnd, inPragDef) $ do - L _ (ITinteger (IL { il_value = col })) <- Lexer.lexer False return - L (RealSrcSpan spF) ITclose_prag <- Lexer.lexer False return + L _ (ITinteger (IL { il_value = col })) <- wrappedLexer + L spF ITclose_prag <- wrappedLexer let newLoc = mkRealSrcLoc (srcSpanFile spF) (srcSpanEndLine spF) (fromIntegral col) (bEnd'', _) <- getInput diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs index b6eb863018..e39aa02659 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs @@ -9,6 +9,8 @@ module Haddock.Backends.Hyperlinker.Renderer (render) where import Haddock.Backends.Hyperlinker.Types import Haddock.Backends.Hyperlinker.Utils +import qualified Data.ByteString as BS + import HieTypes import Module ( ModuleName, moduleNameString ) import Name ( getOccString, isInternalName, Name, nameModule, nameUnique ) @@ -114,6 +116,7 @@ renderWithAst srcs Node{..} toks = anchored $ case toks of renderToken :: Token -> Html renderToken Token{..} + | BS.null tkValue = mempty | tkType == TkSpace = renderSpace (srcSpanStartLine tkSpan) tkValue' | otherwise = tokenSpan ! [ multiclass style ] where diff --git a/hypsrc-test/src/PositionPragmas.hs b/hypsrc-test/src/PositionPragmas.hs new file mode 100644 index 0000000000..907316fd8a --- /dev/null +++ b/hypsrc-test/src/PositionPragmas.hs @@ -0,0 +1,12 @@ +module PositionPragmas where + +{-# LINE 8 "hypsrc-test/src/PositionPragmas.hs" #-} + +foo :: String +foo = bar + +{-# LINE 23 "hypsrc-test/src/PositionPragmas.hs" #-} + +bar :: String +bar = foo + From cdccbe1742e8ae7631c1c8eef564534f8024bede Mon Sep 17 00:00:00 2001 From: Alec Theriault Date: Wed, 30 Jan 2019 11:30:53 -0800 Subject: [PATCH 18/21] Add changelog entry --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 5a9e0d31ee..bb5e845b3a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,12 @@ * Support inline markup in markdown-style links (#875) + * The hyperlinker backend has been re-engineered to use HIE files + and display type annotations on expressions (#977) + + * The hyperlinker backend lexer is now more incremental, faster, and + more memory efficient (#977) + ## Changes in version 2.22.0 * Make `--package-version` optional for `--hoogle` (#899) From bbec706810e9f7861ba8c3abc136cbcbf15cb2d5 Mon Sep 17 00:00:00 2001 From: Alec Theriault Date: Wed, 30 Jan 2019 23:45:57 -0800 Subject: [PATCH 19/21] Fixup var/type classes --- .../Haddock/Backends/Hyperlinker/Renderer.hs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs index e39aa02659..d6ffa683a7 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs @@ -134,7 +134,7 @@ richToken srcs details Token{..} tkValue' = filterCRLF $ utf8DecodeByteString tkValue content = tokenSpan ! [ multiclass style ] tokenSpan = Html.thespan (Html.toHtml tkValue') - style = tokenStyle tkType ++ concatMap richTokenStyle contexts + style = tokenStyle tkType ++ concatMap (richTokenStyle (null (nodeType details))) contexts contexts = concatMap (Set.elems . identInfo) . Map.elems . nodeIdentifiers $ details @@ -171,18 +171,21 @@ annotate ni content = printName :: Either ModuleName Name -> String printName = either moduleNameString getOccString - -richTokenStyle :: ContextInfo -> [StyleClass] -richTokenStyle Use = ["hs-var"] -richTokenStyle IEThing{} = ["hs-var"] -richTokenStyle TyDecl = ["hs-var"] -richTokenStyle ValBind{} = ["hs-var"] -richTokenStyle PatternBind{} = ["hs-var"] -richTokenStyle ClassTyDecl{} = ["hs-var"] -richTokenStyle RecField{} = ["hs-var"] -richTokenStyle Decl{} = ["hs-type"] -richTokenStyle TyVarBind{} = ["hs-type"] -richTokenStyle _ = [] +richTokenStyle + :: Bool -- ^ are we lacking a type annotation? + -> ContextInfo -- ^ in what context did this token show up? + -> [StyleClass] +richTokenStyle True Use = ["hs-type"] +richTokenStyle False Use = ["hs-var"] +richTokenStyle _ RecField{} = ["hs-var"] +richTokenStyle _ PatternBind{} = ["hs-var"] +richTokenStyle _ MatchBind{} = ["hs-var"] +richTokenStyle _ TyVarBind{} = ["hs-type"] +richTokenStyle _ ValBind{} = ["hs-var"] +richTokenStyle _ TyDecl = ["hs-type"] +richTokenStyle _ ClassTyDecl{} = ["hs-type"] +richTokenStyle _ Decl{} = ["hs-var"] +richTokenStyle _ IEThing{} = [] -- could be either a value or type tokenStyle :: TokenType -> [StyleClass] tokenStyle TkIdentifier = ["hs-identifier"] From 9f3106288044ffa113d886ed4b9e48e5756f4d25 Mon Sep 17 00:00:00 2001 From: Alec Theriault Date: Wed, 30 Jan 2019 12:47:37 -0800 Subject: [PATCH 20/21] Accept output of hypsrc-test --- hypsrc-test/ref/src/CPP.html | 197 +- hypsrc-test/ref/src/ClangCppBug.html | 306 ++ hypsrc-test/ref/src/Classes.html | 1652 ++++++---- hypsrc-test/ref/src/Constructors.html | 1268 +++++--- hypsrc-test/ref/src/Identifiers.html | 1485 ++++++--- hypsrc-test/ref/src/LinkingIdentifiers.html | 572 ++++ hypsrc-test/ref/src/Literals.html | 433 ++- hypsrc-test/ref/src/Operators.html | 1540 +++++---- hypsrc-test/ref/src/Polymorphism.html | 3183 +++++++++++-------- hypsrc-test/ref/src/PositionPragmas.html | 172 + hypsrc-test/ref/src/Records.html | 1346 +++++--- hypsrc-test/ref/src/Types.html | 1022 +++--- 12 files changed, 8645 insertions(+), 4531 deletions(-) create mode 100644 hypsrc-test/ref/src/ClangCppBug.html create mode 100644 hypsrc-test/ref/src/LinkingIdentifiers.html create mode 100644 hypsrc-test/ref/src/PositionPragmas.html diff --git a/hypsrc-test/ref/src/CPP.html b/hypsrc-test/ref/src/CPP.html index fb85bd2f01..2ebcae90dc 100644 --- a/hypsrc-test/ref/src/CPP.html +++ b/hypsrc-test/ref/src/CPP.html @@ -11,8 +11,8 @@ > module where - -#define SOMETHING1 + > + +#define SOMETHING1 foofoo :: StringString foofoo :: String +foo "foo" - -"foo"#define SOMETHING2 + > + +#define SOMETHING2 barbar :: StringString barbar :: String +bar = "block comment in a string is not a comment {- " - -"block comment in a string is not a comment {- "#define SOMETHING3 + > + +#define SOMETHING3 -- " single quotes are fine in line comments -- {- unclosed block comments are fine in line comments -- Multiline CPP is also fine -#define FOO\ + > +#define FOO\ 1 bazbaz :: StringString bazbaz :: String +baz = "line comment in a string is not a comment --""line comment in a string is not a comment --"
{-# LANGUAGE CPP #-}
+module ClangCppBug where
+
+foo :: Int
+foo :: Int
+foo = 1
+
+-- Clang doesn't mind these:
+#define BAX 2
+{-# INLINE bar #-}
+
+bar :: Int
+bar :: Int
+bar = 3
+
+-- But it doesn't like this:
+{-# RULES
+"bar/qux" bar = qux
+"qux/foo" qux = foo
+  #-}
+
+qux :: Int
+qux :: Int
+qux = 88
+
\ No newline at end of file diff --git a/hypsrc-test/ref/src/Classes.html b/hypsrc-test/ref/src/Classes.html index d2604e823a..dd1e6ebcd6 100644 --- a/hypsrc-test/ref/src/Classes.html +++ b/hypsrc-test/ref/src/Classes.html @@ -19,36 +19,40 @@ > class FooFoo aa barbar :: aa -> IntInt bazbaz :: IntInt (aa, aa) instance FooFoo IntInt barbar :: Int -> Int +bar = idInt -> Int +forall a. a -> a +id baz xbaz :: Int -> (Int, Int) +baz x :: Int +x (xInt +x, xInt +x) instance Foo [a] where + >instance Foo [a] where bar = length + > bar :: [a] -> Int bar = [a] -> Int +forall (t :: * -> *) a. Foldable t => t a -> Int +length + baz :: Int -> ([a], [a]) +baz baz _ = ([], [])_ = ([], []) class FooFoo aa => Foo'Foo' aa quuxquux (aa, aa)-> aa quux (x, y) = norf [x, y]quux (x :: a +x, y :: a +y) = [a] -> a +forall a. Foo' a => [a] -> a +norf [a +x, a +y] norfnorf [aa]-> aa norf = quux . baz . sum . map barnorf = (a, a) -> a +forall a. Foo' a => (a, a) -> a +quux ((a, a) -> a) -> ([a] -> (a, a)) -> [a] -> a +forall b c a. (b -> c) -> (a -> b) -> a -> c +. Int -> (a, a) +forall a. Foo a => Int -> (a, a) +baz (Int -> (a, a)) -> ([a] -> Int) -> [a] -> (a, a) +forall b c a. (b -> c) -> (a -> b) -> a -> c +. [Int] -> Int +forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a +sum ([Int] -> Int) -> ([a] -> [Int]) -> [a] -> Int +forall b c a. (b -> c) -> (a -> b) -> a -> c +. (a -> Int) -> [a] -> [Int] +forall a b. (a -> b) -> [a] -> [b] +map a -> Int +forall a. Foo a => a -> Int +bar instance Foo' IntFoo' Int norfnorf :: [Int] -> Int +norf = sum[Int] -> Int +forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a +sum instance Foo' [a] where + >instance Foo' [a] where quux = uncurry (++) quux :: ([a], [a]) -> [a] +quux = ([a] -> [a] -> [a]) -> ([a], [a]) -> [a] +forall a b c. (a -> b -> c) -> (a, b) -> c +uncurry [a] -> [a] -> [a] +forall a. [a] -> [a] -> [a] +(++) class PlughPlugh pp plugh :: p a a -> p b b -> p (a -> b) (b -> a)plugh :: p a a -> p b b -> p (a -> b) (b -> a) instance PlughPlugh EitherEither plughplugh :: Either a a -> Either b b -> Either (a -> b) (b -> a) +plugh (LeftLeft aa :: a +a)= Right(b -> a) -> Either (a -> b) (b -> a) +forall a b. b -> Either a b +Right $((b -> a) -> Either (a -> b) (b -> a)) +-> (b -> a) -> Either (a -> b) (b -> a) +forall a b. (a -> b) -> a -> b +$ consta -> b -> a +forall a b. a -> b -> a +const aa +a plughplugh (RightRight aa :: a +a)= Right(b -> a) -> Either (a -> b) (b -> a) +forall a b. b -> Either a b +Right $((b -> a) -> Either (a -> b) (b -> a)) +-> (b -> a) -> Either (a -> b) (b -> a) +forall a b. (a -> b) -> a -> b +$ consta -> b -> a +forall a b. a -> b -> a +const aa +a plughplugh (LeftLeft bb :: b +b)= Left(a -> b) -> Either (a -> b) (b -> a) +forall a b. a -> Either a b +Left $((a -> b) -> Either (a -> b) (b -> a)) +-> (a -> b) -> Either (a -> b) (b -> a) +forall a b. (a -> b) -> a -> b +$ constb -> a -> b +forall a b. a -> b -> a +const bb +b plughplugh (RightRight bb :: b +b)= Left(a -> b) -> Either (a -> b) (b -> a) +forall a b. a -> Either a b +Left $((a -> b) -> Either (a -> b) (b -> a)) +-> (a -> b) -> Either (a -> b) (b -> a) +forall a b. (a -> b) -> a -> b +$ constb -> a -> b +forall a b. a -> b -> a +const bb +b data FooFoo = BarBar | BazBaz | QuuxQuux FooFoo IntInt newtype NorfNorf = NorfNorf (FooFoo, [FooFoo], FooFoo) barbar, bazbaz, quuxquux :: FooFoo barbar :: Foo +bar = BarFoo +Bar bazbaz :: Foo +baz = BazFoo +Baz quuxquux :: Foo +quux = QuuxFoo -> Int -> Foo +Quux quuxFoo +quux 00 unfoounfoo :: FooFoo -> IntInt unfoounfoo :: Foo -> Int +unfoo BarBar = 00 unfoounfoo BazBaz = 00 unfoounfoo (Quux fooQuux nfoo :: Foo +foo n :: Int +n)= 4242 *Int -> Int -> Int +forall a. Num a => a -> a -> a +* nInt +n +Int -> Int -> Int +forall a. Num a => a -> a -> a ++ unfooFoo -> Int +unfoo fooFoo +foo unnorfunnorf :: NorfNorf [FooFoo] unnorfunnorf :: Norf -> [Foo] +unnorf (NorfNorf (BarBar, xsxs :: [Foo] +xs, BarBar)= xs[Foo] +xs unnorfunnorf (NorfNorf (BazBaz, xsxs :: [Foo] +xs, BazBaz)= reverse[Foo] -> [Foo] +forall a. [a] -> [a] +reverse xs[Foo] +xs unnorfunnorf = undefined[Foo] +forall a. HasCallStack => a +undefined unnorf'unnorf' :: NorfNorf -> IntInt unnorf' xunnorf' :: Norf -> Int +unnorf' x :: Norf +x@(NorfNorf (f1f1 :: Foo +f1@(QuuxQuux _ nn :: Int +n), f2f2 :: Foo +f2@(QuuxQuux f3f3 :: Foo +f3 x'Int +x' +Int -> Int -> Int +forall a. Num a => a -> a -> a ++ nInt +n *Int -> Int -> Int +forall a. Num a => a -> a -> a +* unfooFoo -> Int +unfoo f1Foo +f1 +Int -> Int -> Int +forall a. Num a => a -> a -> a ++ auxFoo -> Int +aux f3Foo +f3 aux fxaux :: Foo -> Int +aux fx :: Foo +fx = unfooFoo -> Int +unfoo f2Foo +f2 *Int -> Int -> Int +forall a. Num a => a -> a -> a +* unfooFoo -> Int +unfoo fxFoo +fx *Int -> Int -> Int +forall a. Num a => a -> a -> a +* unfooFoo -> Int +unfoo f3Foo +f3 x'x' :: Int +x' = sum[Int] -> Int +forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a +sum .([Int] -> Int) -> (Norf -> [Int]) -> Norf -> Int +forall b c a. (b -> c) -> (a -> b) -> a -> c +. map(Foo -> Int) -> [Foo] -> [Int] +forall a b. (a -> b) -> [a] -> [b] +map unfooFoo -> Int +unfoo .([Foo] -> [Int]) -> (Norf -> [Foo]) -> Norf -> [Int] +forall b c a. (b -> c) -> (a -> b) -> a -> c +. unnorfNorf -> [Foo] +unnorf $(Norf -> Int) -> Norf -> Int +forall a b. (a -> b) -> a -> b +$ xNorf +x foofoo, barbar, bazbaz :: IntInt -> IntInt -> IntInt foo x yfoo :: Int -> Int -> Int +foo x :: Int +x y :: Int +y = x + xInt +x *Int -> Int -> Int +forall a. Num a => a -> a -> a ++ barInt +x yInt -> Int -> Int +forall a. Num a => a -> a -> a +* xInt -> Int -> Int +bar *Int +y yInt +x +Int -> Int -> Int +forall a. Num a => a -> a -> a +* y + >Int bary xInt -> Int -> Int +forall a. Num a => a -> a -> a ++ Int +y +bar :: Int -> Int -> Int +bar x :: Int +x y :: Int +y = y + xInt +y -Int -> Int -> Int +forall a. Num a => a -> a -> a ++ bazInt +x xInt -> Int -> Int +forall a. Num a => a -> a -> a +- yInt -> Int -> Int +baz -Int +x xInt +y +Int -> Int -> Int +forall a. Num a => a -> a -> a +- y + >Int bazx xInt -> Int -> Int +forall a. Num a => a -> a -> a ++ Int +y +baz :: Int -> Int -> Int +baz x :: Int +x y :: Int +y = xInt +x *Int -> Int -> Int +forall a. Num a => a -> a -> a +* yInt +y *Int -> Int -> Int +forall a. Num a => a -> a -> a +* yInt +y *Int -> Int -> Int +forall a. Num a => a -> a -> a +* yInt +y *Int -> Int -> Int +forall a. Num a => a -> a -> a +* xInt +x quuxquux :: IntInt -> IntInt quux xquux :: Int -> Int +quux x :: Int +x = fooInt -> Int -> Int +foo (barInt -> Int -> Int +bar xInt +x xInt +x) (barInt -> Int -> Int +bar xInt +x xInt +x) norfnorf :: IntInt -> IntInt -> IntInt -> IntInt norf x y znorf :: Int -> Int -> Int -> Int +norf x :: Int +x y :: Int +y z :: Int +z | xInt +x <Int -> Int -> Bool +forall a. Ord a => a -> a -> Bool +< 00 = quuxInt -> Int +quux xInt +x | yInt +y <Int -> Int -> Bool +forall a. Ord a => a -> a -> Bool +< 00 = quuxInt -> Int +quux yInt +y | zInt +z <Int -> Int -> Bool +forall a. Ord a => a -> a -> Bool +< 00 = quuxInt -> Int +quux zInt +z | otherwiseBool +otherwise = norfInt -> Int -> Int -> Int +norf (-xInt +x)(-yInt +y)(-zInt +z) mainmain :: IOIO mainmain :: IO () +main putStrLnString -> IO () +putStrLn .(String -> IO ()) -> (Int -> String) -> Int -> IO () +forall b c a. (b -> c) -> (a -> b) -> a -> c +. showInt -> String +forall a. Show a => a -> String +show $(Int -> IO ()) -> Int -> IO () +forall a b. (a -> b) -> a -> b +$ fooInt -> Int -> Int +foo xInt +x yInt +y putStrLnString -> IO () +putStrLn .(String -> IO ()) -> (Int -> String) -> Int -> IO () +forall b c a. (b -> c) -> (a -> b) -> a -> c +. showInt -> String +forall a. Show a => a -> String +show $(Int -> IO ()) -> Int -> IO () +forall a b. (a -> b) -> a -> b +$ quuxInt -> Int +quux zInt +z putStrLnString -> IO () +putStrLn .(String -> IO ()) -> (Int -> String) -> Int -> IO () +forall b c a. (b -> c) -> (a -> b) -> a -> c +. showInt -> String +forall a. Show a => a -> String +show $(Int -> IO ()) -> Int -> IO () +forall a b. (a -> b) -> a -> b +$ Identifiers.norfInt -> Int -> Int -> Int +Identifiers.norf xInt +x yInt +y zInt +z xx :: Int +x = 1010 yy :: Int +y = 2020 zz :: Int +z = 3030 +> \ No newline at end of file diff --git a/hypsrc-test/ref/src/LinkingIdentifiers.html b/hypsrc-test/ref/src/LinkingIdentifiers.html new file mode 100644 index 0000000000..2ef590bd7b --- /dev/null +++ b/hypsrc-test/ref/src/LinkingIdentifiers.html @@ -0,0 +1,572 @@ +
-- Tests that the identifers/operators are properly linked even when:
+--
+--   * backquoted, parenthesized, vanilla
+--   * qualified, not-qualified
+--
+module LinkingIdentifiers where
+
+ident :: Int -> Int -> Int
+x :: Int
+x ident :: Int -> Int -> Int
+`ident` 2 = (Int
+x Int -> Int -> Int
+`ident` 2) Int -> Int -> Int
+forall a. Num a => a -> a -> a
++ (Int
+x Int -> Int -> Int
+`LinkingIdentifiers.ident` 2)
+ident x :: Int
+x 2 = Int -> Int -> Int
+ident Int
+x 2 Int -> Int -> Int
+forall a. Num a => a -> a -> a
++ Int -> Int -> Int
+LinkingIdentifiers.ident Int
+x 2
+
+(++:++) :: Int -> Int -> Int
+x :: Int
+x ++:++ :: Int -> Int -> Int
+++:++ 2 = (Int
+x Int -> Int -> Int
+++:++ 2) Int -> Int -> Int
+forall a. Num a => a -> a -> a
++ (Int
+x Int -> Int -> Int
+LinkingIdentifiers.++:++ 2)
+(++:++) x :: Int
+x 2 = Int -> Int -> Int
+(++:++) Int
+x 2 Int -> Int -> Int
+forall a. Num a => a -> a -> a
++ Int -> Int -> Int
+(LinkingIdentifiers.++:++) Int
+x 2
+
\ No newline at end of file diff --git a/hypsrc-test/ref/src/Literals.html b/hypsrc-test/ref/src/Literals.html index dfcefc9780..62ea32dd22 100644 --- a/hypsrc-test/ref/src/Literals.html +++ b/hypsrc-test/ref/src/Literals.html @@ -19,238 +19,334 @@ > strstr :: StringString strstr :: String +str = "str literal""str literal" num :: Num a => anum :: Num a => a numnum :: a +num = 00 +a -> a -> a +forall a. Num a => a -> a -> a ++ 11 +a -> a -> a +forall a. Num a => a -> a -> a ++ 10100111010011 *a -> a -> a +forall a. Num a => a -> a -> a +* 4123141231 +a -> a -> a +forall a. Num a => a -> a -> a ++ 1213112131 frac :: Fractional a => afrac :: Fractional a => a fracfrac :: a +frac = 42.000000142.0000001 list :: [[[[a]]]]list :: [[[[a]]]] listlist :: [[[[a]]]] +list pairpair pairpair :: ((), ((), (), ()), ()) +pair (+++) ::(+++) :: [a] -> [a] -> [a] [a] -> [a] +a :: [a] +a +++ :: [a] -> [a] -> [a] ++++ b :: [a] +b ->= [a] + >[a] a +++[a] -> [a] -> [a] +forall a. [a] -> [a] -> [a] +++ [a] +b = a ++ b ++[a] -> [a] -> [a] +forall a. [a] -> [a] -> [a] +++ a[a] +a ($$$) ::($$$) :: [a] -> [a] -> [a] [a] -> [a] +a :: [a] +a $$$ :: [a] -> [a] -> [a] +$$$ b :: [a] +b ->= [a] + >[a] ab $$$[a] -> [a] -> [a] +forall a. [a] -> [a] -> [a] ++++ b[a] +a = b +++ a (***) :: [a] -> [a] -> [a](***) :: [a] -> [a] -> [a] (***) a*** :: [a] -> [a] -> [a] +(***) a :: [a] +a = a[a] +a (***)(***) aa :: [a] +a (_:b:b :: [a] +b)= a[a] +a +++[a] -> [a] -> [a] +forall a. [a] -> [a] -> [a] ++++ (a[a] +a ***[a] -> [a] -> [a] +forall a. [a] -> [a] -> [a] +*** b[a] +b) (*/\*) :: [[a]] -> [a] -> [a](*/\*) :: [[a]] -> [a] -> [a] a */\* ba :: [[a]] +a */\* :: [[a]] -> [a] -> [a] +*/\* b :: [a] +b = concatMap([a] -> [a]) -> [[a]] -> [a] +forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b] +concatMap (***[a] -> [a] -> [a] +forall a. [a] -> [a] -> [a] +*** b[a] +b) a[[a]] +a (**/\**)(**/\**) :: [[a]] -> [[a]] -> [[a]] :: [[a]] +a :: [[a]] +a **/\** :: [[a]] -> [[a]] -> [[a]] +**/\** b :: [[a]] +b ->= [[a]]([[a]] -> [a] -> [a]) -> [[[a]]] -> [[a]] -> [[a]] +forall a b c. (a -> b -> c) -> [a] -> [b] -> [c] +zipWith ->[[a]] -> [a] -> [a] +forall a. [[a]] -> [a] -> [a] +(*/\*) [[a]] + >[[a]] a **/\**[[a]] -> [[a]] -> [[a]] +forall a. [a] -> [a] -> [a] ++++ [[a]] +b = zipWith (*/\*) [a +++ b] (a[[a]] +a $$$[[a]] -> [[a]] -> [[a]] +forall a. [a] -> [a] -> [a] +$$$ b[[a]] +b) (#.#) :: a -> b -> (c -> (a, b))(#.#) :: a -> b -> (c -> (a, b)) a #.# ba :: a +a #.# :: a -> b -> c -> (a, b) +#.# b :: b +b = const(a, b) -> c -> (a, b) +forall a b. a -> b -> a +const $((a, b) -> c -> (a, b)) -> (a, b) -> c -> (a, b) +forall a b. (a -> b) -> a -> b +$ (aa +a, bb +b) {-# LANGUAGE ScopedTypeVariables #-} module - -foo :: a -> a -> a -foo + +foo :: a -> a -> a +foo :: a -> a -> a +foo = undefineda -> a -> a +forall a. HasCallStack => a +undefined foo'foo' forall aa. aa -> aa -> a -foo'a = undefined + >foo' :: a -> a -> a bar :: a -> bfoo' -> (a,= b) -bara -> a -> a +forall a. HasCallStack => a +undefined + +bar :: a -> b -> (a, b) +bar :: a -> b -> (a, b) +bar = undefineda -> b -> (a, b) +forall a. HasCallStack => a +undefined bar'bar' forall a ba b. aa -> bb (aa, bb) bar'bar' :: a -> b -> (a, b) +bar' = undefineda -> b -> (a, b) +forall a. HasCallStack => a +undefined + +baz :: a -> (a -> [a -> a] -> b) -> b +baz :: a -> (a -> [a -> a] -> b) -> b +baz = a -> (a -> [a -> a] -> b) -> b +forall a. HasCallStack => a +undefined bazbaz' :: forall a b. aa (aa [aa -> aa]-> bb)-> bb bazbaz' :: a -> (a -> [a -> a] -> b) -> b +baz' = a -> (a -> [a -> a] -> b) -> b +forall a. HasCallStack => a +undefined + +quux :: a -> (forall a. a -> a) -> a +quux :: a -> (forall a. a -> a) -> a +quux x :: a +x f :: forall a. a -> a +f = undefineda -> a +forall a. a -> a +f a +x baz'quux' forall a ba. aa (aforall ->a. [aa -> aa]) -> b)a +quux' :: a -> (forall a. a -> a) -> a +quux' x :: a +x f :: forall a. a -> a +f ->= ba -> a +forall a. a -> a +f + > a baz'x + + +num :: Num a => a -> a -> a +num :: a -> a -> a +num = undefineda -> a -> a +forall a. HasCallStack => a +undefined quuxnum' :: a -> (forall aa. a -> a) -> a -quux xNum fa = f x - -quux' :: forall=> a. a -> (forall a. a -> a) -> a -quux' x fa = f x - - num :: Num a => a -> a -> a + >num' :: a -> a -> a numnum' = undefined - -num' :: forall a. Num a => a -> a -> a -num'a -> a -> a +forall a. HasCallStack => a +undefined + +eq :: (Eq a, Eq b) => [a] -> [b] -> (a, b) +eq :: [a] -> [b] -> (a, b) +eq = undefined + >[a] -> [b] -> (a, b) +forall a. HasCallStack => a undefined eq :: (Eq a, Eq b) => [a] -> [b] -> (a, b) eqeq' = undefined - -eq' forall a ba b. (Eq a, Eq b) => [a] -> [b] -> (a, b) -eq' = undefined - -mon :: Monad m => (a -> m a) -> m a -mon = undefined - -mon' :: forall m(Eq a., MonadEq mb) ([aa] -> [m ab)] -> (ma, ab) mon'eq' :: [a] -> [b] -> (a, b) +eq' = undefined[a] -> [b] -> (a, b) +forall a. HasCallStack => a +undefined + +mon :: Monad m => (a -> m a) -> m a +mon :: (a -> m a) -> m a +mon + > = (a -> m a) -> m a +forall a. HasCallStack => a undefined norfmon' :: a -> (forall am a. OrdMonad am => (aa -> am a)-> a -norfm xa +mon' :: (a -> m a) -> m a +mon' f= (a -> m a) -> m a +forall a. HasCallStack => a +undefined + + +norf :: a -> (forall a. Ord a => a -> a) -> a +norf :: a -> (forall a. Ord a => a -> a) -> a +norf x :: a +x f :: forall a. Ord a => a -> a +f = xa +x norf'norf' forall aa. aa forall aa. OrdOrd aa => aa -> aa)-> aa norf'norf' :: a -> (forall a. Ord a => a -> a) -> a +norf' xx :: a +x ff :: forall a. Ord a => a -> a +f = xa +x plughplugh forall aa. aa -> aa plughplugh :: a -> a +plugh xx :: a +x = xa +x :: aa thudthud forall a ba b. (aa -> bb)-> aa (aa, bb) thudthud :: (a -> b) -> a -> (a, b) +thud ff :: a -> b +f xx :: a +x (xa +x :: aa, yb +y) (aa, bb) yy :: b +y (fa -> b +f :: aa -> bb) xa +x :: bb
module PositionPragmas where
+
+{-# LINE 8 "hypsrc-test/src/PositionPragmas.hs" #-}
+
+foo :: String
+foo :: String
+foo = String
+bar
+
+{-# LINE 23 "hypsrc-test/src/PositionPragmas.hs" #-}
+
+bar :: String
+bar :: String
+bar = String
+foo 
+
+
\ No newline at end of file diff --git a/hypsrc-test/ref/src/Records.html b/hypsrc-test/ref/src/Records.html index 7d23d114dd..bc99cc56bf 100644 --- a/hypsrc-test/ref/src/Records.html +++ b/hypsrc-test/ref/src/Records.html @@ -11,25 +11,25 @@ > {-# LANGUAGE RecordWildCards #-} module data PointPoint = PointPoint { xPoint -> Int +x !IntInt , yPoint -> Int +y !IntInt
pointpoint :: IntInt -> IntInt
-> PointPoint point x ypoint :: Int -> Int -> Point +point x :: Int +x y :: Int +y = Point$WPoint :: Int -> Int -> Point +Point { xx :: Int +x = xInt +x, yy :: Int +y = yInt +y lengthSqrlengthSqr :: PointPoint -> IntInt lengthSqrlengthSqr :: Point -> Int +lengthSqr (PointPoint { xx :: Point -> Int +x = xInt +x, yy :: Point -> Int +y = yInt +y = xInt +x *Int -> Int -> Int +forall a. Num a => a -> a -> a +* xInt +x +Int -> Int -> Int +forall a. Num a => a -> a -> a ++ yInt +y *Int -> Int -> Int +forall a. Num a => a -> a -> a +* yInt +y lengthSqr'lengthSqr' :: PointPoint -> IntInt lengthSqr'lengthSqr' :: Point -> Int +lengthSqr' (PointPoint { xInt +x :: Int +x :: Point -> Int +x, yInt +y :: Int +y :: Point -> Int +y = yInt +y *Int -> Int -> Int +forall a. Num a => a -> a -> a +* yInt +y +Int -> Int -> Int +forall a. Num a => a -> a -> a ++ xInt +x *Int -> Int -> Int +forall a. Num a => a -> a -> a +* xInt +x translateXtranslateX, translateYtranslateY :: PointPoint -> IntInt -> PointPoint translateX p dtranslateX :: Point -> Int -> Point +translateX p :: Point +p d :: Int +d = pPoint +p { xx :: Int +x = xPoint -> Int +x pPoint +p +Int -> Int -> Int +forall a. Num a => a -> a -> a ++ dInt +d translateY p dtranslateY :: Point -> Int -> Point +translateY p :: Point +p d :: Int +d = pPoint +p { yy :: Int +y = yPoint -> Int +y pPoint +p +Int -> Int -> Int +forall a. Num a => a -> a -> a ++ dInt +d translatetranslate :: IntInt -> IntInt -> PointPoint -> PointPoint translate x y ptranslate :: Int -> Int -> Point -> Point +translate x :: Int +x y :: Int +y p :: Point +p auxPoint -> Point +aux pPoint +p (dxdx :: Int +dx, dydy :: Int +dy) (xInt +x, yInt +y) aux Point{..}aux :: Point -> Point +aux Point{..} = pPoint +p { xx :: Int +x = xInt +x +Int -> Int -> Int +forall a. Num a => a -> a -> a ++ dxInt +dx, yy :: Int +y = yInt +y +Int -> Int -> Int +forall a. Num a => a -> a -> a ++ dyInt +dy module data QuuxQuux = BarBar | BazBaz newtype FooFoo = FooFoo type FooQuuxFooQuux (FooFoo, QuuxQuux) type QuuxFooQuuxFoo (QuuxQuux, FooFoo) datafamily NorfNorf aa bb datainstance NorfNorf FooFoo QuuxQuux = NFQNFQ FooFoo QuuxQuux datainstance NorfNorf QuuxQuux FooFoo = NQFNQF QuuxQuux FooFoo typefamily Norf'Norf' aa bb typeinstance Norf'Norf' FooFoo QuuxQuux (FooFoo, QuuxQuux) typeinstance Norf'Norf' QuuxQuux FooFoo (QuuxQuux, FooFoo) norf1norf1 :: NorfNorf FooFoo QuuxQuux -> IntInt norf1norf1 :: Norf Foo Quux -> Int +norf1 (NFQNFQ (FooFoo ) BarBar)= 00 norf1norf1 (NFQNFQ (FooFoo ) BazBaz)= 11 norf2norf2 :: NorfNorf QuuxQuux FooFoo -> IntInt norf2norf2 :: Norf Quux Foo -> Int +norf2 (NQFNQF BarBar (FooFoo = 00 norf2norf2 (NQFNQF BazBaz (FooFoo = 11 norf1'norf1' :: Norf'Norf' FooFoo QuuxQuux -> IntInt norf1'norf1' :: Norf' Foo Quux -> Int +norf1' (FooFoo , BarBar)= 00 norf1'norf1' (FooFoo , BazBaz)= 11 norf2'norf2' :: Norf'Norf' QuuxQuux FooFoo -> IntInt norf2'norf2' :: Norf' Quux Foo -> Int +norf2' (BarBar, FooFoo = 00 norf2'norf2' (BazBaz, FooFoo = 11 Date: Wed, 30 Jan 2019 18:51:46 -0800 Subject: [PATCH 21/21] INLINE & optimize tiny hyperlinker utilities Calls out to 'spliceURL' are often useless. Also, given the size of these functions, we should tell GHC to inline them. --- .../Haddock/Backends/Hyperlinker/Renderer.hs | 5 +++-- .../src/Haddock/Backends/Hyperlinker/Utils.hs | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs index d6ffa683a7..a4dcb77b68 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Renderer.hs @@ -2,6 +2,7 @@ {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE BangPatterns #-} module Haddock.Backends.Hyperlinker.Renderer (render) where @@ -264,8 +265,8 @@ hyperlink (srcs, srcs') ident = case ident of renderSpace :: Int -> String -> Html -renderSpace _ [] = Html.noHtml -renderSpace line ('\n':rest) = mconcat +renderSpace !_ "" = Html.noHtml +renderSpace !line ('\n':rest) = mconcat [ Html.thespan (Html.toHtml '\n') , lineAnchor (line + 1) , renderSpace (line + 1) rest diff --git a/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs b/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs index 1c2d17588f..4e8b88d23f 100644 --- a/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs +++ b/haddock-api/src/Haddock/Backends/Hyperlinker/Utils.hs @@ -14,13 +14,13 @@ module Haddock.Backends.Hyperlinker.Utils , recoverFullIfaceTypes ) where +import Haddock.Utils import Haddock.Backends.Xhtml.Utils -import FastString ( nilFS ) import GHC import HieTypes ( HieAST(..), HieType(..), HieArgs(..), TypeIndex, HieTypeFlat ) import IfaceType -import Name ( getOccFS, nameModule_maybe ) +import Name ( getOccFS, getOccString ) import Outputable ( showSDoc ) import Var ( VarBndr(..) ) @@ -29,9 +29,11 @@ import System.FilePath.Posix ((), (<.>)) import qualified Data.Array as A +{-# INLINE hypSrcDir #-} hypSrcDir :: FilePath hypSrcDir = "src" +{-# INLINE hypSrcModuleFile #-} hypSrcModuleFile :: Module -> FilePath hypSrcModuleFile m = moduleNameString (moduleName m) <.> "html" @@ -45,20 +47,19 @@ hypSrcModuleUrl = hypSrcModuleFile hypSrcModuleUrl' :: ModuleName -> String hypSrcModuleUrl' = hypSrcModuleFile' +{-# INLINE hypSrcNameUrl #-} hypSrcNameUrl :: Name -> String -hypSrcNameUrl name = spliceURL - Nothing Nothing (Just name) Nothing nameFormat +hypSrcNameUrl = escapeStr . getOccString +{-# INLINE hypSrcLineUrl #-} hypSrcLineUrl :: Int -> String -hypSrcLineUrl line = spliceURL - Nothing Nothing Nothing (Just spn) lineFormat - where - loc = mkSrcLoc nilFS line 1 - spn = mkSrcSpan loc loc +hypSrcLineUrl line = "line-" ++ show line +{-# INLINE hypSrcModuleNameUrl #-} hypSrcModuleNameUrl :: Module -> Name -> String hypSrcModuleNameUrl mdl name = hypSrcModuleUrl mdl ++ "#" ++ hypSrcNameUrl name +{-# INLINE hypSrcModuleLineUrl #-} hypSrcModuleLineUrl :: Module -> Int -> String hypSrcModuleLineUrl mdl line = hypSrcModuleUrl mdl ++ "#" ++ hypSrcLineUrl line