{-# language ApplicativeDo #-} {-# language BlockArguments #-} {-# language DeriveGeneric #-} {-# language FlexibleContexts #-} {-# language LambdaCase #-} {-# language NamedFieldPuns #-} {-# language NoImplicitPrelude #-} {-# language OverloadedLabels #-} {-# language OverloadedStrings #-} {-# language PackageImports #-} 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 ) -- bytestring import Data.ByteString ( ByteString ) -- 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 Avail ( AvailInfo( Avail, AvailTC ) ) import FieldLabel ( FieldLbl( FieldLabel, flSelector ) ) import HieTypes ( BindType( RegularBind ) , ContextInfo( Decl, ValBind, PatternBind, Use, TyDecl, ClassTyDecl ) , DeclType( DataDec, ClassDec, ConDec ) , HieAST( Node, nodeInfo, nodeChildren, nodeSpan ) , HieASTs( HieASTs ) , HieFile( HieFile, hie_asts, hie_exports, hie_module, hie_hs_file, hie_hs_src ) , IdentifierDetails( IdentifierDetails, identInfo ) , NodeInfo( NodeInfo, nodeIdentifiers, nodeAnnotations ) , Scope( ModuleScope ) ) import Module ( Module, moduleStableString ) import Name ( Name, nameModule_maybe, nameOccName ) import OccName ( OccName , isDataOcc , isDataSymOcc , isTcOcc , isTvOcc , isVarOcc , occNameString ) import 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. , moduleSource :: Map Module ByteString } deriving ( Generic ) -- | The empty analysis - the result of analysing zero @.hie@ files. emptyAnalysis :: Analysis emptyAnalysis = Analysis empty mempty 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, hie_hs_src } = do #modulePaths %= Map.insert hie_module hie_hs_file #moduleSource %= Map.insert hie_module hie_hs_src 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 name -> for_ ( nameToDeclaration name ) addExport AvailTC name pieces fields -> do for_ ( nameToDeclaration name ) addExport for_ pieces ( traverse_ addExport . nameToDeclaration ) for_ fields \FieldLabel{ flSelector } -> for_ ( nameToDeclaration flSelector ) addExport where 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 ] ) 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, nodeInfo = NodeInfo{ nodeAnnotations } } = do guard $ ( "FunBind", "HsBindLR" ) `Set.member` nodeAnnotations for_ ( findDeclarations n ) \d -> do define d nodeSpan for_ ( uses n ) \use -> addDependency d use analyseRewriteRule :: ( Alternative m, MonadState Analysis m ) => HieAST a -> m () analyseRewriteRule n@Node{ nodeInfo = NodeInfo{ nodeAnnotations } } = do guard ( ( "HsRule", "RuleDecl" ) `Set.member` nodeAnnotations ) for_ ( uses n ) addImplicitRoot analyseInstanceDeclaration :: ( Alternative m, MonadState Analysis m ) => HieAST a -> m () analyseInstanceDeclaration n@Node{ nodeInfo = NodeInfo{ nodeAnnotations } } = do guard ( ( "ClsInstD", "InstDecl" ) `Set.member` nodeAnnotations ) traverse_ addImplicitRoot ( uses n ) analyseClassDeclaration :: ( Alternative m, MonadState Analysis m ) => HieAST a -> m () analyseClassDeclaration n@Node{ nodeInfo = NodeInfo{ nodeAnnotations } } = do guard ( ( "ClassDecl", "TyClDecl" ) `Set.member` nodeAnnotations ) for_ ( findIdentifiers isClassDeclaration n ) \d -> for_ ( findIdentifiers ( const True ) n ) ( addDependency d ) where isClassDeclaration = not . Set.null . Set.filter \case Decl ClassDec _ -> True _ -> False analyseDataDeclaration :: ( Alternative m, MonadState Analysis m ) => HieAST a -> m () analyseDataDeclaration n@Node { nodeInfo = NodeInfo{ nodeAnnotations } } = do guard ( ( "DataDecl", "TyClDecl" ) `Set.member` nodeAnnotations ) 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, nodeInfo = NodeInfo{ nodeAnnotations } } = if any ( \( _, t ) -> t == "ConDecl" ) nodeAnnotations then pure n else foldMap constructors nodeChildren 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{ nodeInfo = NodeInfo{ nodeIdentifiers }, nodeChildren } = foldMap ( \case ( Left _, _ ) -> mempty ( Right name, IdentifierDetails{ identInfo } ) -> if f identInfo then foldMap pure ( nameToDeclaration name ) else mempty ) ( Map.toList nodeIdentifiers ) <> 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 }