{-# language ApplicativeDo #-} {-# language BlockArguments #-} {-# language DeriveGeneric #-} {-# language DeriveAnyClass #-} {-# language FlexibleContexts #-} {-# language LambdaCase #-} {-# language NamedFieldPuns #-} {-# language NoImplicitPrelude #-} {-# language OverloadedLabels #-} {-# language OverloadedStrings #-} {-# language TupleSections #-} module Weeder ( -- * Analysis Analysis(..) , analyseEvidenceUses , analyseHieFile , emptyAnalysis , outputableDeclarations -- ** Reachability , Root(..) , reachable -- * Declarations , Declaration(..) ) where -- algebraic-graphs import Algebra.Graph ( Graph, edge, empty, overlay, vertex, stars, star, overlays ) import Algebra.Graph.ToGraph ( dfs ) -- base import Control.Applicative ( Alternative ) import Control.Monad ( guard, msum, when, unless, mzero ) import Data.Traversable ( for ) import Data.Maybe ( mapMaybe ) import Data.Foldable ( for_, traverse_, toList ) import Data.Function ( (&) ) import Data.List ( intercalate ) import Data.Monoid ( First( First ), getFirst ) 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 import Data.Tree (Tree) import qualified Data.Tree as Tree -- generic-lens import Data.Generics.Labels () -- ghc import GHC.Data.FastString ( unpackFS ) import GHC.Iface.Ext.Types ( BindType( RegularBind ) , ContextInfo( Decl, ValBind, PatternBind, Use, TyDecl, ClassTyDecl, EvidenceVarBind, RecField ) , DeclType( DataDec, ClassDec, ConDec, SynDec, FamDec ) , EvVarSource ( EvInstBind, cls ) , HieAST( Node, nodeChildren, nodeSpan, sourcedNodeInfo ) , HieASTs( HieASTs ) , HieFile( HieFile, hie_asts, hie_module, hie_hs_file, hie_types ) , HieType( HTyVarTy, HAppTy, HTyConApp, HForAllTy, HFunTy, HQualTy, HLitTy, HCastTy, HCoercionTy ) , HieArgs( HieArgs ) , HieTypeFix( Roll ) , IdentifierDetails( IdentifierDetails, identInfo, identType ) , NodeAnnotation( NodeAnnotation, nodeAnnotType ) , NodeInfo( nodeIdentifiers, nodeAnnotations ) , Scope( ModuleScope ) , RecFieldContext ( RecFieldOcc ) , TypeIndex , getSourcedNodeInfo ) import GHC.Iface.Ext.Utils ( EvidenceInfo( EvidenceInfo, evidenceVar ) , RefMap , findEvidenceUse , getEvidenceTree , hieTypeToIface , recoverFullType ) import GHC.Unit.Module ( Module, moduleStableString ) import GHC.Utils.Outputable ( defaultSDocContext, showSDocOneLine ) import GHC.Iface.Type ( ShowForAllFlag (ShowForAllWhen) , pprIfaceSigmaType , IfaceTyCon (IfaceTyCon, ifaceTyConName) ) import GHC.Types.Name ( Name, nameModule_maybe, nameOccName , OccName , isDataOcc , isDataSymOcc , isTcOcc , isTvOcc , isVarOcc , occNameString ) import GHC.Types.SrcLoc ( RealSrcSpan, realSrcSpanEnd, realSrcSpanStart, srcLocLine ) -- lens import Control.Lens ( (%=) ) -- mtl import Control.Monad.State.Class ( MonadState ) import Control.Monad.Reader.Class ( MonadReader, asks ) -- parallel import Control.Parallel.Strategies ( NFData ) -- transformers import Control.Monad.Trans.Maybe ( runMaybeT ) import Control.Monad.Trans.Reader ( runReaderT ) -- weeder import Weeder.Config ( Config, ConfigType( Config, typeClassRoots, unusedTypes ) ) data Declaration = Declaration { declModule :: Module -- ^ The module this declaration occurs in. , declOccName :: OccName -- ^ The symbol name of a declaration. } deriving ( Eq, Ord, Generic, NFData ) 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 Int) -- ^ A partial mapping between declarations and their line numbers. -- 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 sites, because a declaration may be defined in -- multiple locations, e.g., a type signature for a function separate -- from its definition. , implicitRoots :: Set Root -- ^ Stores information on Declarations that may be automatically marked -- as always reachable. This is used, for example, to capture knowledge -- not yet modelled in weeder, or to mark all instances of a class as -- roots. , 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. , prettyPrintedType :: Map Declaration String -- ^ Used to match against the types of instances and to replace the -- appearance of declarations in the output , requestedEvidence :: Map Declaration (Set Name) -- ^ Map from declarations to the names containing evidence uses that -- should be followed and treated as dependencies of the declaration. -- We use this to be able to delay analysing evidence uses until later, -- allowing us to begin the rest of the analysis before we have read all -- hie files. } deriving ( Generic, NFData ) instance Semigroup Analysis where (<>) (Analysis a1 b1 c1 d1 e1 f1 g1) (Analysis a2 b2 c2 d2 e2 f2 g2)= Analysis (a1 `overlay` a2) (Map.unionWith (<>) b1 b2) (c1 <> c2) (Map.unionWith (<>) d1 d2) (e1 <> e2) (f1 <> f2) (Map.unionWith (<>) g1 g2) instance Monoid Analysis where mempty = emptyAnalysis data AnalysisInfo = AnalysisInfo { currentHieFile :: HieFile , weederConfig :: Config } -- | The empty analysis - the result of analysing zero @.hie@ files. emptyAnalysis :: Analysis emptyAnalysis = Analysis empty mempty mempty mempty mempty mempty mempty -- | A root for reachability analysis. data Root = -- | A given declaration is a root. DeclarationRoot Declaration | -- | We store extra information for instances in order to be able -- to specify e.g. all instances of a class as roots. InstanceRoot Declaration -- ^ Declaration of the instance Declaration -- ^ Declaration of the parent class | -- | All exported declarations in a module are roots. ModuleRoot Module deriving ( Eq, Ord, Generic, NFData ) -- | 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 dependencyGraph ( foldMap rootDeclarations roots ) ) where rootDeclarations = \case DeclarationRoot d -> [ d ] InstanceRoot d _ -> [ d ] -- filter InstanceRoots in `Main.hs` ModuleRoot m -> foldMap Set.toList ( Map.lookup m exports ) -- | The set of all declarations that could possibly -- appear in the output. outputableDeclarations :: Analysis -> Set Declaration outputableDeclarations Analysis{ declarationSites } = Map.keysSet declarationSites -- Generate an initial graph of the current HieFile. initialGraph :: AnalysisInfo -> Graph Declaration initialGraph info = let hf@HieFile{ hie_asts = HieASTs hieAsts } = currentHieFile info Config{ unusedTypes } = weederConfig info asts = Map.elems hieAsts decls = concatMap (toList . findIdentifiers' (const True)) asts in if unusedTypes then stars do (d, IdentifierDetails{identType}, _) <- decls t <- maybe mzero pure identType let ns = Set.toList $ typeToNames (lookupType hf t) ds = mapMaybe nameToDeclaration ns guard $ not (null ds) pure (d, ds) else mempty -- | Incrementally update 'Analysis' with information in a 'HieFile'. analyseHieFile :: (MonadState Analysis m) => Config -> HieFile -> m () analyseHieFile weederConfig hieFile = let info = AnalysisInfo hieFile weederConfig in runReaderT analyseHieFile' info analyseHieFile' :: ( MonadState Analysis m, MonadReader AnalysisInfo m ) => m () analyseHieFile' = do HieFile{ hie_asts = HieASTs hieASTs, hie_module, hie_hs_file } <- asks currentHieFile #modulePaths %= Map.insert hie_module hie_hs_file g <- asks initialGraph #dependencyGraph %= overlay g for_ hieASTs topLevelAnalysis lookupType :: HieFile -> TypeIndex -> HieTypeFix lookupType hf t = recoverFullType t $ hie_types hf lookupPprType :: MonadReader AnalysisInfo m => TypeIndex -> m String lookupPprType t = do hf <- asks currentHieFile pure . renderType $ lookupType hf t where renderType = showSDocOneLine defaultSDocContext . pprIfaceSigmaType ShowForAllWhen . hieTypeToIface -- | Names mentioned within the type. typeToNames :: HieTypeFix -> Set Name typeToNames (Roll t) = case t of HTyVarTy n -> Set.singleton n HAppTy a (HieArgs args) -> typeToNames a <> hieArgsTypes args HTyConApp (IfaceTyCon{ifaceTyConName}) (HieArgs args) -> Set.singleton ifaceTyConName <> hieArgsTypes args HForAllTy _ a -> typeToNames a HFunTy _mult b c -> typeToNames b <> typeToNames c HQualTy a b -> typeToNames a <> typeToNames b HLitTy _ -> mempty HCastTy a -> typeToNames a HCoercionTy -> mempty where hieArgsTypes :: [(Bool, HieTypeFix)] -> Set Name hieArgsTypes = foldMap (typeToNames . snd) . filter fst -- | @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 (DeclarationRoot x) addInstanceRoot :: ( MonadState Analysis m, MonadReader AnalysisInfo m ) => Declaration -> TypeIndex -> Name -> m () addInstanceRoot x t cls = do for_ (nameToDeclaration cls) \cls' -> #implicitRoots %= Set.insert (InstanceRoot x cls') -- since instances will not appear in the output if typeClassRoots is True Config{ typeClassRoots } <- asks weederConfig unless typeClassRoots $ do str <- lookupPprType t #prettyPrintedType %= Map.insert x str define :: MonadState Analysis m => Declaration -> RealSrcSpan -> m () define decl span = when ( realSrcSpanStart span /= realSrcSpanEnd span ) do #declarationSites %= Map.insertWith Set.union decl ( Set.singleton . srcLocLine $ realSrcSpanStart span ) #dependencyGraph %= overlay ( vertex decl ) topLevelAnalysis :: ( MonadState Analysis m, MonadReader AnalysisInfo m ) => HieAST TypeIndex -> m () topLevelAnalysis n@Node{ nodeChildren } = do Config{ unusedTypes } <- asks weederConfig analysed <- runMaybeT ( msum $ [ analyseStandaloneDeriving n , analyseInstanceDeclaration n , analyseBinding n , analyseRewriteRule n , analyseClassDeclaration n , analyseDataDeclaration n , analysePatternSynonyms n ] ++ if unusedTypes then [ analyseTypeSynonym n , analyseFamilyDeclaration n , analyseFamilyInstance n , analyseTypeSignature n ] else [] ) 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 () annsContain :: HieAST a -> (String, String) -> Bool annsContain Node{ sourcedNodeInfo } ann = any (Set.member ann . Set.map unNodeAnnotation . nodeAnnotations) $ getSourcedNodeInfo sourcedNodeInfo analyseBinding :: ( Alternative m, MonadState Analysis m, MonadReader AnalysisInfo m ) => HieAST a -> m () analyseBinding n@Node{ nodeSpan } = do let bindAnns = Set.fromList [("FunBind", "HsBindLR"), ("PatBind", "HsBindLR")] guard $ any (annsContain n) bindAnns for_ ( findDeclarations n ) \d -> do define d nodeSpan requestEvidence n d for_ ( uses n ) $ addDependency d analyseRewriteRule :: ( Alternative m, MonadState Analysis m ) => HieAST a -> m () analyseRewriteRule n = do guard $ annsContain n ("HsRule", "RuleDecl") for_ ( uses n ) addImplicitRoot analyseInstanceDeclaration :: ( Alternative m, MonadState Analysis m, MonadReader AnalysisInfo m ) => HieAST TypeIndex -> m () analyseInstanceDeclaration n@Node{ nodeSpan } = do guard $ annsContain n ("ClsInstD", "InstDecl") for_ ( findEvInstBinds n ) \(d, cs, ids, _) -> do -- This makes instance declarations show up in -- the output if type-class-roots is set to False. define d nodeSpan requestEvidence n d for_ ( uses n ) $ addDependency d case identType ids of Just t -> for_ cs (addInstanceRoot d t) Nothing -> pure () analyseClassDeclaration :: ( Alternative m, MonadState Analysis m, MonadReader AnalysisInfo m ) => HieAST a -> m () analyseClassDeclaration n@Node{ nodeSpan } = do guard $ annsContain n ("ClassDecl", "TyClDecl") for_ ( findIdentifiers isClassDeclaration n ) $ \d -> do define d nodeSpan requestEvidence 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, MonadReader AnalysisInfo m ) => HieAST TypeIndex -> m () analyseDataDeclaration n = do guard $ annsContain n ("DataDecl", "TyClDecl") Config{ unusedTypes } <- asks weederConfig for_ ( foldMap ( First . Just ) ( findIdentifiers ( any isDataDec ) n ) ) \dataTypeName -> do when unusedTypes $ define dataTypeName (nodeSpan n) -- Without connecting constructors to the data declaration TypeAliasGADT.hs -- fails with a false positive for A conDecs <- for ( constructors n ) \constructor -> for ( foldMap ( First . Just ) ( findIdentifiers ( any isConDec ) constructor ) ) \conDec -> do addDependency conDec dataTypeName pure conDec -- To keep acyclicity in record declarations let isDependent d = Just d `elem` fmap getFirst conDecs for_ ( uses n ) (\d -> unless (isDependent d) $ addDependency dataTypeName d) for_ ( derivedInstances n ) \(d, cs, ids, ast) -> do define d (nodeSpan ast) requestEvidence ast d for_ ( uses ast ) $ addDependency d case identType ids of Just t -> for_ cs (addInstanceRoot d t) Nothing -> pure () where isDataDec = \case Decl DataDec _ -> True _ -> False isConDec = \case Decl ConDec _ -> True _ -> False constructors :: HieAST a -> Seq ( HieAST a ) constructors = findNodeTypes "ConDecl" derivedInstances :: HieAST a -> Seq (Declaration, Set Name, IdentifierDetails a, HieAST a) derivedInstances n = findNodeTypes "HsDerivingClause" n >>= findEvInstBinds findNodeTypes :: String -> HieAST a -> Seq ( HieAST a ) findNodeTypes t n@Node{ nodeChildren, sourcedNodeInfo } = if any (any ( (t ==) . unpackFS . nodeAnnotType) . nodeAnnotations) (getSourcedNodeInfo sourcedNodeInfo) then pure n else foldMap (findNodeTypes t) nodeChildren analyseStandaloneDeriving :: ( Alternative m, MonadState Analysis m, MonadReader AnalysisInfo m ) => HieAST TypeIndex -> m () analyseStandaloneDeriving n@Node{ nodeSpan } = do guard $ annsContain n ("DerivDecl", "DerivDecl") for_ (findEvInstBinds n) \(d, cs, ids, _) -> do define d nodeSpan requestEvidence n d for_ (uses n) $ addDependency d case identType ids of Just t -> for_ cs (addInstanceRoot d t) Nothing -> pure () analyseTypeSynonym :: ( Alternative m, MonadState Analysis m ) => HieAST a -> m () analyseTypeSynonym n@Node{ nodeSpan } = do guard $ annsContain n ("SynDecl", "TyClDecl") for_ ( findIdentifiers isTypeSynonym n ) $ \d -> do define d nodeSpan for_ (uses n) (addDependency d) where isTypeSynonym = any \case Decl SynDec _ -> True _ -> False analyseFamilyDeclaration :: ( Alternative m, MonadState Analysis m ) => HieAST a -> m () analyseFamilyDeclaration n@Node{ nodeSpan } = do guard $ annsContain n ("FamDecl", "TyClDecl") for_ ( findIdentifiers isFamDec n ) $ \d -> do define d nodeSpan for_ (uses n) (addDependency d) where isFamDec = any \case Decl FamDec _ -> True _ -> False analyseFamilyInstance :: ( Alternative m, MonadState Analysis m ) => HieAST a -> m () analyseFamilyInstance n = do guard $ annsContain n ("TyFamInstD", "InstDecl") for_ ( uses n ) addImplicitRoot analyseTypeSignature :: ( Alternative m, MonadState Analysis m ) => HieAST a -> m () analyseTypeSignature n = do guard $ annsContain n ("TypeSig", "Sig") for_ (findIdentifiers isTypeSigDecl n) $ for_ ( uses n ) . addDependency where isTypeSigDecl = any \case TyDecl -> True _ -> False analysePatternSynonyms :: ( Alternative m, MonadState Analysis m ) => HieAST a -> m () analysePatternSynonyms n = do guard $ annsContain n ("PatSynBind", "HsBindLR") for_ ( findDeclarations n ) $ for_ ( uses n ) . addDependency findEvInstBinds :: HieAST a -> Seq (Declaration, Set Name, IdentifierDetails a, HieAST a) findEvInstBinds n = (\(d, ids, ast) -> (d, getClassNames ids, ids, ast)) <$> findIdentifiers' ( not . Set.null . getEvVarSources ) n where getEvVarSources :: Set ContextInfo -> Set EvVarSource getEvVarSources = foldMap (maybe mempty Set.singleton) . Set.map \case EvidenceVarBind a@EvInstBind{} ModuleScope _ -> Just a _ -> Nothing getClassNames :: IdentifierDetails a -> Set Name getClassNames = Set.map cls . getEvVarSources . identInfo 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 = fmap (\(d, _, _) -> d) . findIdentifiers' f -- | Version of findIdentifiers containing more information, -- namely the IdentifierDetails of the declaration and the -- node it was found in. findIdentifiers' :: ( Set ContextInfo -> Bool ) -> HieAST a -> Seq (Declaration, IdentifierDetails a, HieAST a) findIdentifiers' f n@Node{ sourcedNodeInfo, nodeChildren } = foldMap (\case ( Left _, _ ) -> mempty ( Right name, ids@IdentifierDetails{ identInfo } ) -> if f identInfo then (, ids, n) <$> 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 (any isUse) isUse :: ContextInfo -> Bool isUse = \case Use -> True -- not RecFieldMatch and RecFieldDecl because they occur under -- data declarations, which we do not want to add as dependencies -- because that would make the graph no longer acyclic -- RecFieldAssign will be most likely accompanied by the constructor RecField RecFieldOcc _ -> True _ -> False 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) -- | Add evidence uses found under the given node to 'requestedEvidence'. requestEvidence :: ( MonadState Analysis m, MonadReader AnalysisInfo m ) => HieAST a -> Declaration -> m () requestEvidence n d = do Config{ typeClassRoots } <- asks weederConfig -- If type-class-roots flag is set then we don't need to follow -- evidence uses as the binding sites will be roots anyway unless typeClassRoots $ #requestedEvidence %= Map.insertWith (<>) d (Set.fromList names) where names = concat . Tree.flatten $ evidenceUseTree n evidenceUseTree :: HieAST a -> Tree [Name] evidenceUseTree Node{ sourcedNodeInfo, nodeChildren } = Tree.Node { Tree.rootLabel = concatMap (findEvidenceUse . nodeIdentifiers) (getSourcedNodeInfo sourcedNodeInfo) , Tree.subForest = map evidenceUseTree nodeChildren } -- | Follow the given evidence uses back to their instance bindings, -- and connect the declaration to those bindings. followEvidenceUses :: RefMap TypeIndex -> Declaration -> Set Name -> Graph Declaration followEvidenceUses refMap d names = let getEvidenceTrees = mapMaybe (getEvidenceTree refMap) . Set.toList evidenceInfos = concatMap Tree.flatten (getEvidenceTrees names) instanceEvidenceInfos = evidenceInfos & filter \case EvidenceInfo _ _ _ (Just (EvInstBind _ _, ModuleScope, _)) -> True _ -> False evBindSiteDecls = mapMaybe (nameToDeclaration . evidenceVar) instanceEvidenceInfos in star d evBindSiteDecls -- | Follow evidence uses listed under 'requestedEvidence' back to their -- instance bindings, and connect their corresponding declaration to those bindings. analyseEvidenceUses :: RefMap TypeIndex -> Analysis -> Analysis analyseEvidenceUses rf a@Analysis{ requestedEvidence, dependencyGraph } = let graphs = map (uncurry (followEvidenceUses rf)) $ Map.toList requestedEvidence in a { dependencyGraph = overlays (dependencyGraph : graphs) }