weeder-2.4.0: src/Weeder.hs
{-# language ApplicativeDo #-}
{-# language BlockArguments #-}
{-# language DeriveGeneric #-}
{-# language FlexibleContexts #-}
{-# language LambdaCase #-}
{-# language NamedFieldPuns #-}
{-# language NoImplicitPrelude #-}
{-# language OverloadedLabels #-}
{-# language OverloadedStrings #-}
module Weeder
( -- * Analysis
Analysis(..)
, analyseHieFile
, emptyAnalysis
, allDeclarations
-- ** Reachability
, Root(..)
, reachable
-- * Declarations
, Declaration(..)
)
where
-- algebraic-graphs
import Algebra.Graph ( Graph, edge, empty, overlay, vertex, vertexList )
import Algebra.Graph.ToGraph ( dfs )
-- base
import Control.Applicative ( Alternative )
import Control.Monad ( guard, msum, when )
import Data.Foldable ( for_, traverse_ )
import Data.List ( intercalate )
import Data.Monoid ( First( First ) )
import GHC.Generics ( Generic )
import Prelude hiding ( span )
-- containers
import Data.Map.Strict ( Map )
import qualified Data.Map.Strict as Map
import Data.Sequence ( Seq )
import Data.Set ( Set )
import qualified Data.Set as Set
-- generic-lens
import Data.Generics.Labels ()
-- ghc
import GHC.Data.FastString ( unpackFS )
import GHC.Types.Avail
( AvailInfo( Avail, AvailTC )
, GreName( NormalGreName, FieldGreName )
)
import GHC.Types.FieldLabel ( FieldLabel( FieldLabel, flSelector ) )
import GHC.Iface.Ext.Types
( BindType( RegularBind )
, ContextInfo( Decl, ValBind, PatternBind, Use, TyDecl, ClassTyDecl )
, DeclType( DataDec, ClassDec, ConDec )
, HieAST( Node, nodeChildren, nodeSpan, sourcedNodeInfo )
, HieASTs( HieASTs )
, HieFile( HieFile, hie_asts, hie_exports, hie_module, hie_hs_file )
, IdentifierDetails( IdentifierDetails, identInfo )
, NodeAnnotation( NodeAnnotation, nodeAnnotType )
, NodeInfo( nodeIdentifiers, nodeAnnotations )
, Scope( ModuleScope )
, getSourcedNodeInfo
)
import GHC.Unit.Module ( Module, moduleStableString )
import GHC.Types.Name
( Name, nameModule_maybe, nameOccName
, OccName
, isDataOcc
, isDataSymOcc
, isTcOcc
, isTvOcc
, isVarOcc
, occNameString
)
import GHC.Types.SrcLoc ( RealSrcSpan, realSrcSpanEnd, realSrcSpanStart )
-- lens
import Control.Lens ( (%=) )
-- mtl
import Control.Monad.State.Class ( MonadState )
-- transformers
import Control.Monad.Trans.Maybe ( runMaybeT )
data Declaration =
Declaration
{ declModule :: Module
-- ^ The module this declaration occurs in.
, declOccName :: OccName
-- ^ The symbol name of a declaration.
}
deriving
( Eq, Ord )
instance Show Declaration where
show =
declarationStableName
declarationStableName :: Declaration -> String
declarationStableName Declaration { declModule, declOccName } =
let
namespace
| isVarOcc declOccName = "var"
| isTvOcc declOccName = "tv"
| isTcOcc declOccName = "tc"
| isDataOcc declOccName = "data"
| isDataSymOcc declOccName = "dataSym"
| otherwise = "unknown"
in
intercalate "$" [ namespace, moduleStableString declModule, "$", occNameString declOccName ]
-- | All information maintained by 'analyseHieFile'.
data Analysis =
Analysis
{ dependencyGraph :: Graph Declaration
-- ^ A graph between declarations, capturing dependencies.
, declarationSites :: Map Declaration ( Set RealSrcSpan )
-- ^ A partial mapping between declarations and their definition site.
-- This Map is partial as we don't always know where a Declaration was
-- defined (e.g., it may come from a package without source code).
-- We capture a set of spans, because a declaration may be defined in
-- multiple locations, e.g., a type signature for a function separate
-- from its definition.
, implicitRoots :: Set Declaration
-- ^ The Set of all Declarations that are always reachable. This is used
-- to capture knowledge not yet modelled in weeder, such as instance
-- declarations depending on top-level functions.
, exports :: Map Module ( Set Declaration )
-- ^ All exports for a given module.
, modulePaths :: Map Module FilePath
-- ^ A map from modules to the file path to the .hs file defining them.
}
deriving
( Generic )
-- | The empty analysis - the result of analysing zero @.hie@ files.
emptyAnalysis :: Analysis
emptyAnalysis = Analysis empty mempty mempty mempty mempty
-- | A root for reachability analysis.
data Root
= -- | A given declaration is a root.
DeclarationRoot Declaration
| -- | All exported declarations in a module are roots.
ModuleRoot Module
deriving
( Eq, Ord )
-- | Determine the set of all declaration reachable from a set of roots.
reachable :: Analysis -> Set Root -> Set Declaration
reachable Analysis{ dependencyGraph, exports } roots =
Set.fromList ( dfs ( foldMap rootDeclarations roots ) dependencyGraph )
where
rootDeclarations = \case
DeclarationRoot d -> [ d ]
ModuleRoot m -> foldMap Set.toList ( Map.lookup m exports )
-- | The set of all known declarations, including usages.
allDeclarations :: Analysis -> Set Declaration
allDeclarations Analysis{ dependencyGraph } =
Set.fromList ( vertexList dependencyGraph )
-- | Incrementally update 'Analysis' with information in a 'HieFile'.
analyseHieFile :: MonadState Analysis m => HieFile -> m ()
analyseHieFile HieFile{ hie_asts = HieASTs hieASTs, hie_exports, hie_module, hie_hs_file } = do
#modulePaths %= Map.insert hie_module hie_hs_file
for_ hieASTs \ast -> do
addAllDeclarations ast
topLevelAnalysis ast
for_ hie_exports ( analyseExport hie_module )
analyseExport :: MonadState Analysis m => Module -> AvailInfo -> m ()
analyseExport m = \case
Avail (NormalGreName name) ->
traverse_ addExport $ nameToDeclaration name
Avail (FieldGreName (FieldLabel{ flSelector })) ->
traverse_ addExport $ nameToDeclaration flSelector
AvailTC name pieces -> do
for_ ( nameToDeclaration name ) addExport
for_ pieces \case
NormalGreName name ->
traverse_ addExport $ nameToDeclaration name
FieldGreName (FieldLabel{ flSelector }) ->
traverse_ addExport $ nameToDeclaration flSelector
where
addExport :: MonadState Analysis m => Declaration -> m ()
addExport d = #exports %= Map.insertWith (<>) m ( Set.singleton d )
-- | @addDependency x y@ adds the information that @x@ depends on @y@.
addDependency :: MonadState Analysis m => Declaration -> Declaration -> m ()
addDependency x y =
#dependencyGraph %= overlay ( edge x y )
addImplicitRoot :: MonadState Analysis m => Declaration -> m ()
addImplicitRoot x =
#implicitRoots %= Set.insert x
define :: MonadState Analysis m => Declaration -> RealSrcSpan -> m ()
define decl span =
when ( realSrcSpanStart span /= realSrcSpanEnd span ) do
#declarationSites %= Map.insertWith Set.union decl ( Set.singleton span )
#dependencyGraph %= overlay ( vertex decl )
addDeclaration :: MonadState Analysis m => Declaration -> m ()
addDeclaration decl =
#dependencyGraph %= overlay ( vertex decl )
-- | Try and add vertices for all declarations in an AST - both
-- those declared here, and those referred to from here.
addAllDeclarations :: ( MonadState Analysis m ) => HieAST a -> m ()
addAllDeclarations n@Node{ nodeChildren } = do
for_ ( findIdentifiers ( const True ) n ) addDeclaration
for_ nodeChildren addAllDeclarations
topLevelAnalysis :: MonadState Analysis m => HieAST a -> m ()
topLevelAnalysis n@Node{ nodeChildren } = do
analysed <-
runMaybeT
( msum
[
-- analyseStandaloneDeriving n
-- ,
analyseInstanceDeclaration n
, analyseBinding n
, analyseRewriteRule n
, analyseClassDeclaration n
, analyseDataDeclaration n
, analysePatternSynonyms n
]
)
case analysed of
Nothing ->
-- We didn't find a top level declaration here, check all this nodes
-- children.
traverse_ topLevelAnalysis nodeChildren
Just () ->
-- Top level analysis succeeded, there's nothing more to do for this node.
return ()
analyseBinding :: ( Alternative m, MonadState Analysis m ) => HieAST a -> m ()
analyseBinding n@Node{ nodeSpan, sourcedNodeInfo } = do
let bindAnns = Set.fromList [("FunBind", "HsBindLR"), ("PatBind", "HsBindLR")]
guard $ any (not . Set.disjoint bindAnns . Set.map unNodeAnnotation . nodeAnnotations) $ getSourcedNodeInfo sourcedNodeInfo
for_ ( findDeclarations n ) \d -> do
define d nodeSpan
for_ ( uses n ) $ addDependency d
analyseRewriteRule :: ( Alternative m, MonadState Analysis m ) => HieAST a -> m ()
analyseRewriteRule n@Node{ sourcedNodeInfo } = do
guard $ any (Set.member ("HsRule", "RuleDecl") . Set.map unNodeAnnotation . nodeAnnotations) $ getSourcedNodeInfo sourcedNodeInfo
for_ ( uses n ) addImplicitRoot
analyseInstanceDeclaration :: ( Alternative m, MonadState Analysis m ) => HieAST a -> m ()
analyseInstanceDeclaration n@Node{ sourcedNodeInfo } = do
guard $ any (Set.member ("ClsInstD", "InstDecl") . Set.map unNodeAnnotation . nodeAnnotations) $ getSourcedNodeInfo sourcedNodeInfo
traverse_ addImplicitRoot ( uses n )
analyseClassDeclaration :: ( Alternative m, MonadState Analysis m ) => HieAST a -> m ()
analyseClassDeclaration n@Node{ sourcedNodeInfo } = do
guard $ any (Set.member ("ClassDecl", "TyClDecl") . Set.map unNodeAnnotation . nodeAnnotations) $ getSourcedNodeInfo sourcedNodeInfo
for_ ( findIdentifiers isClassDeclaration n ) $
for_ ( findIdentifiers ( const True ) n ) . addDependency
where
isClassDeclaration =
not . Set.null . Set.filter \case
Decl ClassDec _ ->
True
_ ->
False
analyseDataDeclaration :: ( Alternative m, MonadState Analysis m ) => HieAST a -> m ()
analyseDataDeclaration n@Node{ sourcedNodeInfo } = do
guard $ any (Set.member ("DataDecl", "TyClDecl") . Set.map unNodeAnnotation . nodeAnnotations) $ getSourcedNodeInfo sourcedNodeInfo
for_
( foldMap
( First . Just )
( findIdentifiers ( any isDataDec ) n )
)
\dataTypeName ->
for_ ( constructors n ) \constructor ->
for_ ( foldMap ( First . Just ) ( findIdentifiers ( any isConDec ) constructor ) ) \conDec -> do
addDependency conDec dataTypeName
for_ ( uses constructor ) ( addDependency conDec )
where
isDataDec = \case
Decl DataDec _ -> True
_ -> False
isConDec = \case
Decl ConDec _ -> True
_ -> False
constructors :: HieAST a -> Seq ( HieAST a )
constructors n@Node{ nodeChildren, sourcedNodeInfo } =
if any (any ( ("ConDecl" ==) . unpackFS . nodeAnnotType) . nodeAnnotations) (getSourcedNodeInfo sourcedNodeInfo) then
pure n
else
foldMap constructors nodeChildren
analysePatternSynonyms :: ( Alternative m, MonadState Analysis m ) => HieAST a -> m ()
analysePatternSynonyms n@Node{ sourcedNodeInfo } = do
guard $ any (Set.member ("PatSynBind", "HsBindLR") . Set.map unNodeAnnotation . nodeAnnotations) $ getSourcedNodeInfo sourcedNodeInfo
for_ ( findDeclarations n ) $ for_ ( uses n ) . addDependency
findDeclarations :: HieAST a -> Seq Declaration
findDeclarations =
findIdentifiers
( not
. Set.null
. Set.filter \case
-- Things that count as declarations
ValBind RegularBind ModuleScope _ -> True
PatternBind ModuleScope _ _ -> True
Decl _ _ -> True
TyDecl -> True
ClassTyDecl{} -> True
-- Anything else is not a declaration
_ -> False
)
findIdentifiers
:: ( Set ContextInfo -> Bool )
-> HieAST a
-> Seq Declaration
findIdentifiers f Node{ sourcedNodeInfo, nodeChildren } =
foldMap
( \case
( Left _, _ ) ->
mempty
( Right name, IdentifierDetails{ identInfo } ) ->
if f identInfo then
foldMap pure ( nameToDeclaration name )
else
mempty
)
(foldMap (Map.toList . nodeIdentifiers) (getSourcedNodeInfo sourcedNodeInfo))
<> foldMap ( findIdentifiers f ) nodeChildren
uses :: HieAST a -> Set Declaration
uses =
foldMap Set.singleton
. findIdentifiers \identInfo -> Use `Set.member` identInfo
nameToDeclaration :: Name -> Maybe Declaration
nameToDeclaration name = do
m <- nameModule_maybe name
return Declaration { declModule = m, declOccName = nameOccName name }
unNodeAnnotation :: NodeAnnotation -> (String, String)
unNodeAnnotation (NodeAnnotation x y) = (unpackFS x, unpackFS y)