hlint 2.2.8 → 2.2.9
raw patch · 23 files changed
+202/−170 lines, 23 filesdep ~ghc-lib-parser-exPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: ghc-lib-parser-ex
API changes (from Hackage documentation)
Files
- CHANGES.txt +8/−0
- README.md +2/−1
- data/hlint.yaml +5/−0
- hlint.cabal +3/−3
- src/Config/Haskell.hs +1/−1
- src/Config/Type.hs +6/−5
- src/Config/Yaml.hs +16/−15
- src/GHC/Util.hs +8/−33
- src/GHC/Util/DynFlags.hs +1/−1
- src/GHC/Util/HsDecl.hs +7/−1
- src/GHC/Util/HsExpr.hs +4/−4
- src/GHC/Util/HsExtendInstances.hs +36/−0
- src/GHC/Util/Scope.hs +5/−4
- src/GHC/Util/W.hs +0/−47
- src/Grep.hs +1/−1
- src/Hint/Duplicate.hs +1/−1
- src/Hint/Extensions.hs +9/−1
- src/Hint/List.hs +3/−3
- src/Hint/ListRec.hs +6/−6
- src/Hint/Match.hs +35/−31
- src/Hint/Monad.hs +4/−0
- src/Hint/Restrict.hs +39/−9
- src/Util.hs +2/−3
CHANGES.txt view
@@ -1,5 +1,13 @@ Changelog for HLint (* = breaking change) +2.2.9, released 2020-01-27+ Add any/map and all/map fusion hints+ #837, don't warn about redundant do for BlockArguments+ #842, fix parsing of <% operators in hlint.yaml files+ #839, match hints inside instances+ #833, UnboxedTuples can be necessary from newtype deriving+ #817, add the ability to blacklist identifiers from a module+ #834, move not out of any and all 2.2.8, released 2020-01-22 #802, suggest lambda instead of lambda-case for single alts #811, add some foldMap/map hints
README.md view
@@ -325,9 +325,10 @@ - modules: - {name: [Data.Set, Data.HashSet], as: Set} - {name: Control.Arrow, within: []}+ - {name: Control.Monad.State, badidents: [modify, get, put], message: "Use Control.Monad.State.Class instead"} ``` -This fragment requires that all imports of `Set` must be `qualified Data.Set as Set`, enforcing consistency. It also ensures the module `Control.Arrow` can't be used anywhere.+This fragment requires that all imports of `Set` must be `qualified Data.Set as Set`, enforcing consistency. It also ensures the module `Control.Arrow` can't be used anywhere. It also prevents explicit imports of the `modify` identifier from `Control.Monad.State` (this is meant to allow you to prevent people from importing reexported identifiers). You can customize the `Note:` for restricted modules, functions and extensions, by providing a `message` field (default: `may break the code`).
data/hlint.yaml view
@@ -154,6 +154,8 @@ - warn: {lhs: concatMap id, rhs: concat} - warn: {lhs: or (map p x), rhs: any p x} - warn: {lhs: and (map p x), rhs: all p x}+ - warn: {lhs: any f (map g x), rhs: any (f . g) x}+ - warn: {lhs: all f (map g x), rhs: all (f . g) x} - warn: {lhs: "zipWith (,)", rhs: zip} - warn: {lhs: "zipWith3 (,,)", rhs: zip3} - hint: {lhs: length x == 0, rhs: null x, note: IncreasesLaziness}@@ -181,6 +183,8 @@ - warn: {lhs: "filter f x /= []", rhs: any f x} - warn: {lhs: any id, rhs: or} - warn: {lhs: all id, rhs: and}+ - warn: {lhs: any (not . f) x, rhs: not (all f x), name: Hoist not}+ - warn: {lhs: all (not . f) x, rhs: not (any f x), name: Hoist not} - warn: {lhs: any ((==) a), rhs: elem a, note: ValidInstance Eq a} - warn: {lhs: any (== a), rhs: elem a} - warn: {lhs: any (a ==), rhs: elem a, note: ValidInstance Eq a}@@ -986,6 +990,7 @@ # no = sequenceA (pure a) # {-# LANGUAGE QuasiQuotes #-}; no = f (\url -> [hamlet|foo @{url}|]) # yes = f ((,) x) -- (x,)+# instance Class X where method = map f (map g x) -- map (f . g) x # import Prelude \ # yes = flip mapM -- Control.Monad.forM
hlint.cabal view
@@ -1,7 +1,7 @@ cabal-version: >= 1.18 build-type: Simple name: hlint-version: 2.2.8+version: 2.2.9 license: BSD3 license-file: LICENSE category: Development@@ -68,7 +68,7 @@ refact >= 0.3, aeson >= 1.1.2.0, filepattern >= 0.1.1,- ghc-lib-parser-ex == 8.8.2+ ghc-lib-parser-ex == 8.8.3.* if !flag(ghc-lib) && impl(ghc >= 8.8.0) && impl(ghc < 8.9.0) build-depends: ghc == 8.8.*,@@ -121,7 +121,7 @@ GHC.Util.Module GHC.Util.Outputable GHC.Util.SrcLoc- GHC.Util.W+ GHC.Util.HsExtendInstances GHC.Util.DynFlags GHC.Util.RdrName GHC.Util.Scope
src/Config/Haskell.hs view
@@ -55,7 +55,7 @@ [SettingMatchExp $ HintRule severity (head $ snoc names defaultHintName) s (fromParen lhs) (fromParen rhs) a b -- Todo : Replace these with "proper" GHC expressions.- (wrap mempty) (wrap unit) (wrap unit) Nothing]+ (extendInstances' mempty) (extendInstances' unit) (extendInstances' unit) Nothing] | otherwise = [SettingClassify $ Classify severity n a b | n <- names2, (a,b) <- readFuncs bod] where names = filter (not . null) $ getNames pats bod
src/Config/Type.hs view
@@ -98,11 +98,11 @@ ,hintRuleRHS :: Exp SrcSpanInfo -- ^ RHS ,hintRuleSide :: Maybe (Exp SrcSpanInfo) -- ^ Side condition, typically specified with @where _ = ...@. ,hintRuleNotes :: [Note] -- ^ Notes about application of the hint.- -- We wrap these GHC elements in 'W' in order that we may derive 'Show'.- ,hintRuleGhcScope :: W Scope' -- ^ Module scope in which the hint operates (GHC parse tree).- ,hintRuleGhcLHS :: W (HsSyn.LHsExpr HsSyn.GhcPs) -- ^ LHS (GHC parse tree).- ,hintRuleGhcRHS :: W (HsSyn.LHsExpr HsSyn.GhcPs) -- ^ RHS (GHC parse tree).- ,hintRuleGhcSide :: Maybe (W (HsSyn.LHsExpr HsSyn.GhcPs)) -- ^ Side condition (GHC parse tree).+ -- We wrap these GHC elements in 'HsExtendInstances' in order that we may derive 'Show'.+ ,hintRuleGhcScope :: HsExtendInstances Scope' -- ^ Module scope in which the hint operates (GHC parse tree).+ ,hintRuleGhcLHS :: HsExtendInstances (HsSyn.LHsExpr HsSyn.GhcPs) -- ^ LHS (GHC parse tree).+ ,hintRuleGhcRHS :: HsExtendInstances (HsSyn.LHsExpr HsSyn.GhcPs) -- ^ RHS (GHC parse tree).+ ,hintRuleGhcSide :: Maybe (HsExtendInstances (HsSyn.LHsExpr HsSyn.GhcPs)) -- ^ Side condition (GHC parse tree). } deriving Show @@ -114,6 +114,7 @@ ,restrictName :: [String] ,restrictAs :: [String] -- for RestrictModule only, what module names you can import it as ,restrictWithin :: [(String, String)]+ ,restrictBadIdents :: [String] ,restrictMessage :: Maybe String } deriving Show
src/Config/Yaml.hs view
@@ -30,7 +30,7 @@ import qualified Outputable import qualified HsSyn import GHC.Util (baseDynFlags, Scope',scopeCreate')-import GHC.Util.W+import GHC.Util.HsExtendInstances -- | Read a config file in YAML format. Takes a filename, and optionally the contents. -- Fails if the YAML doesn't parse or isn't valid HLint YAML@@ -58,14 +58,14 @@ data Package = Package {packageName :: String ,packageModules :: [ImportDecl S]- ,packageGhcModules :: [W (HsSyn.LImportDecl HsSyn.GhcPs)]+ ,packageGhcModules :: [HsExtendInstances (HsSyn.LImportDecl HsSyn.GhcPs)] } deriving Show data Group = Group {groupName :: String ,groupEnabled :: Bool ,groupImports :: [Either String (ImportDecl S)] -- Left for package imports- ,groupGhcImports :: [Either String (W (HsSyn.LImportDecl HsSyn.GhcPs))]+ ,groupGhcImports :: [Either String (HsExtendInstances (HsSyn.LImportDecl HsSyn.GhcPs))] ,groupRules :: [Either HintRule Classify] -- HintRule has scope set to mempty } deriving Show @@ -198,7 +198,7 @@ parsePackage v = do packageName <- parseField "name" v >>= parseString packageModules <- parseField "modules" v >>= parseArray >>= mapM (parseHSE parseImportDeclWithMode)- packageGhcModules <- parseField "modules" v >>= parseArray >>= mapM (fmap wrap <$> parseGHC parseImportDeclGhcWithMode)+ packageGhcModules <- parseField "modules" v >>= parseArray >>= mapM (fmap extendInstances' <$> parseGHC parseImportDeclGhcWithMode) allowFields v ["name","modules"] return Package{..} @@ -238,7 +238,7 @@ x <- parseString v case word1 x of ("package", x) -> return $ Left x- _ -> Right . wrap <$> parseGHC parseImportDeclGhcWithMode v+ _ -> Right . extendInstances' <$> parseGHC parseImportDeclGhcWithMode v ruleToGroup :: [Either HintRule Classify] -> Group ruleToGroup = Group "" True [] []@@ -254,13 +254,13 @@ hintRuleName <- parseFieldOpt "name" v >>= maybe (return $ guessName hintRuleLHS hintRuleRHS) parseString hintRuleSide <- parseFieldOpt "side" v >>= maybe (return Nothing) (fmap Just . parseHSE parseExpWithMode) - hintRuleGhcLHS <- parseField "lhs" v >>= fmap wrap . parseGHC parseExpGhcWithMode- hintRuleGhcRHS <- parseField "rhs" v >>= fmap wrap . parseGHC parseExpGhcWithMode- hintRuleGhcSide <- parseFieldOpt "side" v >>= maybe (return Nothing) (fmap (Just . wrap) . parseGHC parseExpGhcWithMode)+ hintRuleGhcLHS <- parseField "lhs" v >>= fmap extendInstances' . parseGHC parseExpGhcWithMode+ hintRuleGhcRHS <- parseField "rhs" v >>= fmap extendInstances' . parseGHC parseExpGhcWithMode+ hintRuleGhcSide <- parseFieldOpt "side" v >>= maybe (return Nothing) (fmap (Just . extendInstances') . parseGHC parseExpGhcWithMode) allowFields v ["lhs","rhs","note","name","side"] let hintRuleScope = mempty :: Scope- let hintRuleGhcScope = wrap mempty :: W Scope'+ let hintRuleGhcScope = extendInstances' mempty :: HsExtendInstances Scope' return [Left HintRule{hintRuleSeverity=severity, ..}] else do names <- parseFieldOpt "name" v >>= maybe (return []) parseArrayString@@ -274,13 +274,14 @@ Just def -> do b <- parseBool def allowFields v ["default"]- return $ Restrict restrictType b [] [] [] Nothing+ return $ Restrict restrictType b [] [] [] [] Nothing Nothing -> do restrictName <- parseFieldOpt "name" v >>= maybe (return []) parseArrayString restrictWithin <- parseFieldOpt "within" v >>= maybe (return [("","")]) (parseArray >=> concatMapM parseWithin) restrictAs <- parseFieldOpt "as" v >>= maybe (return []) parseArrayString+ restrictBadIdents <- parseFieldOpt "badidents" v >>= maybe (pure []) parseArrayString restrictMessage <- parseFieldOpt "message" v >>= maybeParse parseString- allowFields v $ ["as" | restrictType == RestrictModule] ++ ["name","within", "message"]+ allowFields v $ ["as" | restrictType == RestrictModule] ++ ["badidents", "name", "within", "message"] return Restrict{restrictDefault=True,..} parseWithin :: Val -> Parser [(String, String)] -- (module, decl)@@ -330,7 +331,7 @@ groups = [x | ConfigGroup x <- configs] settings = concat [x | ConfigSetting x <- configs] packageMap = Map.fromListWith (++) [(packageName, packageModules) | Package{..} <- packages]- packageMap' = Map.fromListWith (++) [(packageName, fmap unwrap packageGhcModules) | Package{..} <- packages]+ packageMap' = Map.fromListWith (++) [(packageName, fmap unExtendInstances' packageGhcModules) | Package{..} <- packages] groupMap = Map.fromListWith (\new old -> new) [(groupName, groupEnabled) | Group{..} <- groups] f Group{..}@@ -338,7 +339,7 @@ | otherwise = map (either (\r -> SettingMatchExp r{hintRuleScope=scope,hintRuleGhcScope=scope'}) SettingClassify) groupRules where scope = asScope packageMap groupImports- scope'= asScope' packageMap' (map (fmap unwrap) groupGhcImports)+ scope'= asScope' packageMap' (map (fmap unExtendInstances') groupGhcImports) asScope :: Map.HashMap String [ImportDecl S] -> [Either String (ImportDecl S)] -> Scope asScope packages xs = scopeCreate $ Module an Nothing [] (concatMap f xs) []@@ -347,8 +348,8 @@ f (Left x) | Just pkg <- Map.lookup x packages = pkg | otherwise = error $ "asScope failed to do lookup, " ++ x -asScope' :: Map.HashMap String [HsSyn.LImportDecl HsSyn.GhcPs] -> [Either String (HsSyn.LImportDecl HsSyn.GhcPs)] -> W Scope'-asScope' packages xs = W $ scopeCreate' (HsSyn.HsModule Nothing Nothing (concatMap f xs) [] Nothing Nothing)+asScope' :: Map.HashMap String [HsSyn.LImportDecl HsSyn.GhcPs] -> [Either String (HsSyn.LImportDecl HsSyn.GhcPs)] -> HsExtendInstances Scope'+asScope' packages xs = HsExtendInstances $ scopeCreate' (HsSyn.HsModule Nothing Nothing (concatMap f xs) [] Nothing Nothing) where f (Right x) = [x] f (Left x) | Just pkg <- Map.lookup x packages = pkg
src/GHC/Util.hs view
@@ -11,13 +11,14 @@ , module GHC.Util.Module , module GHC.Util.Outputable , module GHC.Util.SrcLoc- , module GHC.Util.W+ , module GHC.Util.HsExtendInstances , module GHC.Util.DynFlags , module GHC.Util.Scope , module GHC.Util.RdrName , module GHC.Util.Unify - , parsePragmasIntoDynFlags, parseFileGhcLib, parseExpGhcLib, parseImportGhcLib+ , parsePragmasIntoDynFlags+ , parseFileGhcLib, parseExpGhcLib, parseImportGhcLib ) where import GHC.Util.View@@ -31,56 +32,30 @@ import GHC.Util.Module import GHC.Util.Outputable import GHC.Util.SrcLoc-import GHC.Util.W+import GHC.Util.HsExtendInstances import GHC.Util.DynFlags import GHC.Util.RdrName import GHC.Util.Scope import GHC.Util.Unify import qualified Language.Haskell.GhclibParserEx.Parse as GhclibParserEx+import Language.Haskell.GhclibParserEx.DynFlags (parsePragmasIntoDynFlags) import HsSyn import Lexer-import Parser import SrcLoc-import FastString-import StringBuffer-import GHC.LanguageExtensions.Type import DynFlags -import Data.List.Extra import System.FilePath import Language.Preprocessor.Unlit parseExpGhcLib :: String -> DynFlags -> ParseResult (LHsExpr GhcPs)-parseExpGhcLib = GhclibParserEx.parseExpr+parseExpGhcLib = GhclibParserEx.parseExpression parseImportGhcLib :: String -> DynFlags -> ParseResult (LImportDecl GhcPs) parseImportGhcLib = GhclibParserEx.parseImport --- TODO(SF): GhclibParserEx.parseFile doesn't take 'unlit' into--- consideration yet. parseFileGhcLib :: FilePath -> String -> DynFlags -> ParseResult (Located (HsModule GhcPs)) parseFileGhcLib filename str flags =- Lexer.unP Parser.parseModule parseState- where- location = mkRealSrcLoc (mkFastString filename) 1 1- buffer = stringToStringBuffer $- if takeExtension filename /= ".lhs" then str else unlit filename str- parseState = mkPState flags buffer location---- TODO(SF): GhclibParserEx.parsePragmasIntoDynFlags doesn't take--- enabled/disabled extensions into consideration yet.-parsePragmasIntoDynFlags :: DynFlags- -> ([Extension], [Extension])- -> FilePath- -> String- -> IO (Either String DynFlags)-parsePragmasIntoDynFlags flags (enable, disable) filepath str = do- flags <- GhclibParserEx.parsePragmasIntoDynFlags flags filepath str- case flags of- Right flags -> do- let flags' = foldl' xopt_set flags enable- let flags'' = foldl' xopt_unset flags' disable- return $ Right flags''- err -> return err+ GhclibParserEx.parseFile filename flags+ (if takeExtension filename /= ".lhs" then str else unlit filename str)
src/GHC/Util/DynFlags.hs view
@@ -3,7 +3,7 @@ import DynFlags import GHC.LanguageExtensions.Type import Data.List.Extra-import Language.Haskell.GhclibParserEx.Parse+import Language.Haskell.GhclibParserEx.Config baseDynFlags :: DynFlags baseDynFlags =
src/GHC/Util/HsDecl.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE NamedFieldPuns #-} -module GHC.Util.HsDecl (declName,isForD',isNewType',isDerivD',isClsDefSig')+module GHC.Util.HsDecl (declName,bindName,isForD',isNewType',isDerivD',isClsDefSig') where import HsSyn@@ -37,6 +37,12 @@ ForD _ ForeignExport{fd_name} -> Just $ unLoc fd_name _ -> Nothing declName _ = Nothing {- COMPLETE LL-}+++bindName :: LHsBind GhcPs -> Maybe String+bindName (LL _ FunBind{fun_id}) = Just $ occNameString $ occName $ unLoc fun_id+bindName (LL _ VarBind{var_id}) = Just $ occNameString $ occName var_id+bindName _ = Nothing isClsDefSig' :: Sig GhcPs -> Bool isClsDefSig' (ClassOpSig _ True _ _) = True; isClsDefSig' _ = False
src/GHC/Util/HsExpr.hs view
@@ -35,7 +35,7 @@ import GHC.Util.Brackets import GHC.Util.View import GHC.Util.FreeVars-import GHC.Util.W+import GHC.Util.HsExtendInstances import GHC.Util.Pat import Control.Applicative@@ -238,10 +238,10 @@ factor y@(LL _ (HsApp _ ini lst)) | view' lst == Var_' x = Just (ini, [ini]) factor y@(LL _ (HsApp _ ini lst)) | Just (z, ss) <- factor lst = let r = niceDotApp' ini z- in if eqLoc' r z then Just (r, ss) else Just (r, ini : ss)+ in if astEq' r z then Just (r, ss) else Just (r, ini : ss) factor (LL _ (OpApp _ y op (factor -> Just (z, ss))))| isDol' op = let r = niceDotApp' y z- in if eqLoc' r z then Just (r, ss) else Just (r, y : ss)+ in if astEq' r z then Just (r, ss) else Just (r, y : ss) factor (LL _ (HsPar _ y@(LL _ HsApp{}))) = factor y factor _ = Nothing -- Rewrite '\x y -> x + y' as '(+)'.@@ -321,7 +321,7 @@ reduce1' (LL loc (HsApp _ len (LL _ (ExplicitList _ _ xs)))) | varToStr' len == "length" = cL loc $ HsLit noExt (HsInt noExt (IL NoSourceText False n)) where n = fromIntegral $ length xs-reduce1' (view' -> App2' op (LL _ (HsLit _ x)) (LL _ (HsLit _ y))) | varToStr' op == "==" = strToVar' (show (eqLoc' x y))+reduce1' (view' -> App2' op (LL _ (HsLit _ x)) (LL _ (HsLit _ y))) | varToStr' op == "==" = strToVar' (show (astEq' x y)) reduce1' (view' -> App2' op (LL _ (HsLit _ (HsInt _ x))) (LL _ (HsLit _ (HsInt _ y)))) | varToStr' op == ">=" = strToVar' $ show (x >= y) reduce1' (view' -> App2' op x y) | varToStr' op == "&&" && varToStr' x == "True" = y
+ src/GHC/Util/HsExtendInstances.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module GHC.Util.HsExtendInstances (+ HsExtendInstances(..)+ , extendInstances', unExtendInstances'+ , astEq', astListEq') where++import Outputable+import Data.Function++newtype HsExtendInstances a =+ HsExtendInstances a deriving Outputable -- Wrapper of terms.+-- The issue is that at times, terms we work with in this program are+-- not in `Eq` and `Ord` and we need them to be. This work-around+-- resorts to implementing `Eq` and `Ord` for the these types via+-- lexicographical comparisons of string representations. As long as+-- two different terms never map to the same string representation,+-- basing `Eq` and `Ord` on their string representations rather than+-- the term types themselves, leads to identical results.+toStr :: Outputable a => HsExtendInstances a -> String+toStr (HsExtendInstances e) = Outputable.showSDocUnsafe $ Outputable.ppr e+instance Outputable a => Eq (HsExtendInstances a) where (==) a b = toStr a == toStr b+instance Outputable a => Ord (HsExtendInstances a) where compare = compare `on` toStr+instance Outputable a => Show (HsExtendInstances a) where show = toStr++extendInstances' :: a -> HsExtendInstances a+extendInstances' = HsExtendInstances++unExtendInstances' :: HsExtendInstances a -> a+unExtendInstances' (HsExtendInstances x) = x++astEq' :: Outputable a => a -> a -> Bool+astEq' a b = extendInstances' a == extendInstances' b++astListEq' :: Outputable a => [a] -> [a] -> Bool+astListEq' as bs = length as == length bs && all (uncurry astEq') (zip as bs)
src/GHC/Util/Scope.hs view
@@ -19,6 +19,7 @@ import Outputable import Data.List+import Data.List.Extra import Data.Maybe -- A scope is a list of import declarations.@@ -66,10 +67,10 @@ -- scope that will refer to the same thing. If the resulting name is -- ambiguous, pick a plausible candidate. scopeMove' :: (Scope', Located RdrName) -> Scope' -> Located RdrName-scopeMove' (a, x@(fromQual' -> Just name)) (Scope' b)- | null imps = head $ real ++ [x]- | any (not . ideclQualified) imps = unqual' x- | otherwise = noLoc $ mkRdrQual (unLoc $ head (mapMaybe ideclAs imps ++ map ideclName imps)) name+scopeMove' (a, x@(fromQual' -> Just name)) (Scope' b) = case imps of+ [] -> head $ real ++ [x]+ imp:_ | all ideclQualified imps -> noLoc $ mkRdrQual (unLoc . fromMaybe (ideclName imp) $ firstJust ideclAs imps) name+ | otherwise -> unqual' x where real :: [Located RdrName] real = [noLoc $ mkRdrQual (mkModuleName m) name | m <- possModules' a x]
− src/GHC/Util/W.hs
@@ -1,47 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--module GHC.Util.W (- W(..)- , wrap, unwrap- , eqLoc', eqNoLoc', eqNoLocLists') where--import Outputable-import SrcLoc--import GHC.Util.DynFlags-import GHC.Util.SrcLoc--import Data.Function-import Data.Data-import Data.Generics.Uniplate.Data ()--newtype W a = W a deriving Outputable -- Wrapper of terms.--- The issue is that at times, terms we work with in this program are--- not in `Eq` and `Ord` and we need them to be. This work-around--- resorts to implementing `Eq` and `Ord` for the these types via--- lexicographical comparisons of string representations. As long as--- two different terms never map to the same string representation,--- basing `Eq` and `Ord` on their string representations rather than--- the term types themselves, leads to identical results.-wToStr :: Outputable a => W a -> String-wToStr (W e) = showPpr baseDynFlags e-instance Outputable a => Eq (W a) where (==) a b = wToStr a == wToStr b-instance Outputable a => Ord (W a) where compare = compare `on` wToStr-instance Outputable a => Show (W a) where show = wToStr--wrap :: a -> W a-wrap = W--unwrap :: W a -> a-unwrap (W x) = x---- Compare two terms for absolute equality.-eqLoc' :: Outputable a => a -> a -> Bool-eqLoc' a b = wrap a == wrap b---- Compare two terms for equality modulo locs.-eqNoLoc' :: (Data a, Outputable a, HasSrcSpan a) => a -> a -> Bool-eqNoLoc' a b = wrap (stripLocs' a) == wrap (stripLocs' b)--eqNoLocLists' :: (Data a, Outputable a, HasSrcSpan a) => [a] -> [a] -> Bool-eqNoLocLists' as bs = length as == length bs && all (uncurry eqNoLoc') (zip as bs)
src/Grep.hs view
@@ -27,7 +27,7 @@ let unit = GHC.noLoc $ GHC.ExplicitTuple GHC.noExt [] GHC.Boxed let rule = hintRules [HintRule Suggestion "grep" scope exp (Tuple an Boxed []) Nothing [] -- Todo : Replace these with "proper" GHC expressions.- (wrap mempty) (wrap unit) (wrap unit) Nothing]+ (extendInstances' mempty) (extendInstances' unit) (extendInstances' unit) Nothing] forM_ files $ \file -> do res <- parseModuleEx flags file Nothing case res of
src/Hint/Duplicate.hs view
@@ -67,7 +67,7 @@ | ((m1, d1, SrcSpanD p1), (m2, d2, SrcSpanD p2), xs) <- duplicateOrdered 3 $ map f ys] where f (m, d, xs) =- [((m, d, SrcSpanD (getLoc x)), wrap (stripLocs' x)) | x <- xs]+ [((m, d, SrcSpanD (getLoc x)), extendInstances' (stripLocs' x)) | x <- xs] --------------------------------------------------------------------- -- DUPLICATE FINDING
src/Hint/Extensions.hs view
@@ -76,6 +76,8 @@ f :: x -> (x, x); f x = (x, x) -- {-# LANGUAGE UnboxedTuples #-} \ f x = case x of (# a, b #) -> a+{-# LANGUAGE GeneralizedNewtypeDeriving,UnboxedTuples #-} \+newtype T m a = T (m a) deriving (PrimMonad) {-# LANGUAGE DefaultSignatures #-} \ class Val a where; val :: a -- {-# LANGUAGE DefaultSignatures #-} \@@ -285,7 +287,13 @@ used RecordWildCards = hasS hasFieldsDotDot' ||^ hasS hasPFieldsDotDot' used RecordPuns = hasS isPFieldPun' ||^ hasS isFieldPun' used NamedFieldPuns = hasS isPFieldPun' ||^ hasS isFieldPun'-used UnboxedTuples = has isUnboxedTuple' ||^ has (== Unboxed)+used UnboxedTuples = has isUnboxedTuple' ||^ has (== Unboxed) ||^ hasS isDeriving+ where+ -- detect if there are deriving declarations or data ... deriving stuff+ -- by looking for the deriving strategy both contain (even if its Nothing)+ -- see https://github.com/ndmitchell/hlint/issues/833 for why we care+ isDeriving :: Maybe (LDerivStrategy GhcPs) -> Bool+ isDeriving _ = True used PackageImports = hasS f where f :: ImportDecl GhcPs -> Bool
src/Hint/List.hs view
@@ -94,7 +94,7 @@ list_comp_aux e xs | "False" `elem` cons = [suggest' "Short-circuited list comprehension" o o' (suggestExpr o o')] | "True" `elem` cons = [suggest' "Redundant True guards" o o2 (suggestExpr o o2)]- | not (eqNoLocLists' xs ys) = [suggest' "Move guards forward" o o3 (suggestExpr o o3)]+ | not (astListEq' xs ys) = [suggest' "Move guards forward" o o3 (suggestExpr o o3)] | otherwise = [] where ys = moveGuardsForward xs@@ -258,6 +258,6 @@ g :: LHsType GhcPs -> [Idea] g e@(fromTyParen' -> x) = [suggest' "Use String" x (transform f x) rs | not . null $ rs]- where f x = if eqNoLoc' x typeListChar then typeString else x- rs = [Replace Type (toSS' t) [] (unsafePrettyPrint typeString) | t <- universe x, eqNoLoc' t typeListChar]+ where f x = if astEq' x typeListChar then typeString else x+ rs = [Replace Type (toSS' t) [] (unsafePrettyPrint typeString) | t <- universe x, astEq' t typeListChar] stringType _ = [] -- {-# COMPLETE LL #-}
src/Hint/ListRec.hs view
@@ -98,25 +98,25 @@ matchListRec o@(ListCase vs nil (x, xs, cons)) -- Suggest 'map'? | [] <- vs, varToStr' nil == "[]", (LL _ (OpApp _ lhs c rhs)) <- cons, varToStr' c == ":"- , eqNoLoc' (fromParen' rhs) recursive, xs `notElem` vars' lhs+ , astEq' (fromParen' rhs) recursive, xs `notElem` vars' lhs = Just $ (,,) "map" Hint.Type.Warning $ appsBracket' [ strToVar' "map", niceLambda' [x] lhs, strToVar' xs] -- Suggest 'foldr'? | [] <- vs, App2' op lhs rhs <- view' cons , xs `notElem` (vars' op ++ vars' lhs) -- the meaning of xs changes, see #793- , eqNoLoc' (fromParen' rhs) recursive+ , astEq' (fromParen' rhs) recursive = Just $ (,,) "foldr" Suggestion $ appsBracket' [ strToVar' "foldr", niceLambda' [x] $ appsBracket' [op,lhs], nil, strToVar' xs] -- Suggest 'foldl'? | [v] <- vs, view' nil == Var_' v, (LL _ (HsApp _ r lhs)) <- cons- , eqNoLoc' (fromParen' r) recursive+ , astEq' (fromParen' r) recursive , xs `notElem` vars' lhs = Just $ (,,) "foldl" Suggestion $ appsBracket' [ strToVar' "foldl", niceLambda' [v,x] lhs, strToVar' v, strToVar' xs] -- Suggest 'foldM'? | [v] <- vs, (LL _ (HsApp _ ret res)) <- nil, isReturn' ret, varToStr' res == "()" || view' res == Var_' v , [LL _ (BindStmt _ (view' -> PVar_' b1) e _ _), LL _ (BodyStmt _ (fromParen' -> (LL _ (HsApp _ r (view' -> Var_' b2)))) _ _)] <- asDo cons- , b1 == b2, eqNoLoc' r recursive, xs `notElem` vars' e+ , b1 == b2, astEq' r recursive, xs `notElem` vars' e , name <- "foldM" ++ ['_' | varToStr' res == "()"] = Just $ (,,) name Suggestion $ appsBracket' [strToVar' name, niceLambda' [v,x] e, strToVar' v, strToVar' xs]@@ -185,11 +185,11 @@ eliminateArgs :: [String] -> LHsExpr GhcPs -> ([String], LHsExpr GhcPs) eliminateArgs ps cons = (remove ps, transform f cons) where- args = [zs | z : zs <- map fromApps' $ universeApps' cons, eqNoLoc' z recursive]+ args = [zs | z : zs <- map fromApps' $ universeApps' cons, astEq' z recursive] elim = [all (\xs -> length xs > i && view' (xs !! i) == Var_' p) args | (i, p) <- zip [0..] ps] ++ repeat False remove = concat . zipWith (\b x -> [x | not b]) elim - f (fromApps' -> x : xs) | eqNoLoc' x recursive = apps' $ x : remove xs+ f (fromApps' -> x : xs) | astEq' x recursive = apps' $ x : remove xs f x = x
src/Hint/Match.hs view
@@ -1,4 +1,5 @@-{-# LANGUAGE PatternGuards, ViewPatterns, RecordWildCards, FlexibleContexts, ScopedTypeVariables #-}+{-# LANGUAGE RecordWildCards, NamedFieldPuns, TupleSections #-}+{-# LANGUAGE PatternGuards, ViewPatterns, FlexibleContexts, ScopedTypeVariables #-} {- The matching does a fairly simple unification between the two terms, treating@@ -51,6 +52,7 @@ import Config.Type import Data.Generics.Uniplate.Operations +import Bag import HsSyn import SrcLoc import BasicTypes@@ -63,23 +65,23 @@ readMatch' settings = findIdeas' (concatMap readRule' settings) readRule' :: HintRule -> [HintRule]-readRule' m@HintRule{ hintRuleGhcLHS=(stripLocs' . unwrap -> hintRuleGhcLHS)- , hintRuleGhcRHS=(stripLocs' . unwrap -> hintRuleGhcRHS)- , hintRuleGhcSide=((stripLocs' . unwrap <$>) -> hintRuleGhcSide)+readRule' m@HintRule{ hintRuleGhcLHS=(stripLocs' . unExtendInstances' -> hintRuleGhcLHS)+ , hintRuleGhcRHS=(stripLocs' . unExtendInstances' -> hintRuleGhcRHS)+ , hintRuleGhcSide=((stripLocs' . unExtendInstances' <$>) -> hintRuleGhcSide) } =- (:) m{ hintRuleGhcLHS=wrap hintRuleGhcLHS- , hintRuleGhcRHS=wrap hintRuleGhcRHS- , hintRuleGhcSide=wrap <$> hintRuleGhcSide } $ do+ (:) m{ hintRuleGhcLHS=extendInstances' hintRuleGhcLHS+ , hintRuleGhcRHS=extendInstances' hintRuleGhcRHS+ , hintRuleGhcSide=extendInstances' <$> hintRuleGhcSide } $ do (l, v1) <- dotVersion' hintRuleGhcLHS (r, v2) <- dotVersion' hintRuleGhcRHS guard $ v1 == v2 && not (null l) && (length l > 1 || length r > 1) && Set.notMember v1 (Set.map occNameString (freeVars' $ maybeToList hintRuleGhcSide ++ l ++ r)) if not (null r) then- [ m{ hintRuleGhcLHS=wrap (dotApps' l), hintRuleGhcRHS=wrap (dotApps' r), hintRuleGhcSide=wrap <$> hintRuleGhcSide }- , m{ hintRuleGhcLHS=wrap (dotApps' (l ++ [strToVar' v1])), hintRuleGhcRHS=wrap (dotApps' (r ++ [strToVar' v1])), hintRuleGhcSide=wrap <$> hintRuleGhcSide } ]+ [ m{ hintRuleGhcLHS=extendInstances' (dotApps' l), hintRuleGhcRHS=extendInstances' (dotApps' r), hintRuleGhcSide=extendInstances' <$> hintRuleGhcSide }+ , m{ hintRuleGhcLHS=extendInstances' (dotApps' (l ++ [strToVar' v1])), hintRuleGhcRHS=extendInstances' (dotApps' (r ++ [strToVar' v1])), hintRuleGhcSide=extendInstances' <$> hintRuleGhcSide } ] else if length l > 1 then- [ m{ hintRuleGhcLHS=wrap (dotApps' l), hintRuleGhcRHS=wrap (strToVar' "id"), hintRuleGhcSide=wrap <$> hintRuleGhcSide }- , m{ hintRuleGhcLHS=wrap (dotApps' (l++[strToVar' v1])), hintRuleGhcRHS=wrap (strToVar' v1), hintRuleGhcSide=wrap <$> hintRuleGhcSide}]+ [ m{ hintRuleGhcLHS=extendInstances' (dotApps' l), hintRuleGhcRHS=extendInstances' (strToVar' "id"), hintRuleGhcSide=extendInstances' <$> hintRuleGhcSide }+ , m{ hintRuleGhcLHS=extendInstances' (dotApps' (l++[strToVar' v1])), hintRuleGhcRHS=extendInstances' (strToVar' v1), hintRuleGhcSide=extendInstances' <$> hintRuleGhcSide}] else [] -- Find a dot version of this rule, return the sequence of app@@ -105,30 +107,32 @@ findIdeas' :: [HintRule] -> Scope' -> ModuleEx -> LHsDecl GhcPs -> [Idea] findIdeas' matches s _ decl = timed "Hint" "Match apply" $ forceList [ (idea' (hintRuleSeverity m) (hintRuleName m) x y [r]){ideaNote=notes}- | decl <- findDecls' decl- , (parent,x) <- universeParentExp' decl- , m <- matches, Just (y, notes, subst) <- [matchIdea' s decl m parent x]- , let r = R.Replace R.Expr (toSS' x) subst (unsafePrettyPrint $ unwrap (hintRuleGhcRHS m))+ | (name, expr) <- findDecls' decl+ , (parent,x) <- universeParentExp' expr+ , m <- matches, Just (y, notes, subst) <- [matchIdea' s name m parent x]+ , let r = R.Replace R.Expr (toSS' x) subst (unsafePrettyPrint $ unExtendInstances' (hintRuleGhcRHS m)) ] -findDecls' :: LHsDecl GhcPs -> [LHsDecl GhcPs]-findDecls' x@(LL _ InstD{}) = children x+-- | A list of root expressions, with their associated names+findDecls' :: LHsDecl GhcPs -> [(String, LHsExpr GhcPs)]+findDecls' x@(LL _ (InstD _ (ClsInstD _ ClsInstDecl{cid_binds}))) =+ [(fromMaybe "" $ bindName xs, x) | xs <- bagToList cid_binds, x <- childrenBi xs] findDecls' (LL _ RuleD{}) = [] -- Often rules contain things that HLint would rewrite.-findDecls' x = [x]+findDecls' x = map (fromMaybe "" $ declName x,) $ childrenBi x matchIdea' :: Scope'- -> LHsDecl GhcPs+ -> String -> HintRule -> Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> Maybe (LHsExpr GhcPs, [Note], [(String, R.SrcSpan)])-matchIdea' sb decl HintRule{..} parent x = do- let lhs = unwrap hintRuleGhcLHS- rhs = unwrap hintRuleGhcRHS- sa = unwrap hintRuleGhcScope+matchIdea' sb declName HintRule{..} parent x = do+ let lhs = unExtendInstances' hintRuleGhcLHS+ rhs = unExtendInstances' hintRuleGhcRHS+ sa = unExtendInstances' hintRuleGhcScope nm a b = scopeMatch' (sa, a) (sb, b) u <- unifyExp' nm True lhs x- u <- validSubst' eqNoLoc' u+ u <- validSubst' astEq' u -- Need to check free vars before unqualification, but after subst -- (with 'e') need to unqualify before substitution (with 'res').@@ -143,8 +147,8 @@ -- what free vars they make use of. guard $ not (any isLambda' $ universe lhs) || not (any isQuasiQuote' $ universe x) - guard $ checkSide' (unwrap <$> hintRuleGhcSide) $ ("original", x) : ("result", res) : fromSubst' u- guard $ checkDefine' decl parent res+ guard $ checkSide' (unExtendInstances' <$> hintRuleGhcSide) $ ("original", x) : ("result", res) : fromSubst' u+ guard $ checkDefine' declName parent res return (res, hintRuleNotes, [(s, toSS' pos) | (s, pos) <- fromSubst' u, getLoc pos /= noSrcSpan]) @@ -158,15 +162,15 @@ bool (LL _ (OpApp _ x op y)) | varToStr' op == "&&" = bool x && bool y | varToStr' op == "||" = bool x || bool y- | varToStr' op == "==" = expr (fromParen1' x) `eqNoLoc'` expr (fromParen1' y)+ | varToStr' op == "==" = expr (fromParen1' x) `astEq'` expr (fromParen1' y) bool (LL _ (HsApp _ x y)) | varToStr' x == "not" = not $ bool y bool (LL _ (HsPar _ x)) = bool x bool (LL _ (HsApp _ cond (sub -> y))) | 'i' : 's' : typ <- varToStr' cond = isType typ y bool (LL _ (HsApp _ (LL _ (HsApp _ cond (sub -> x))) (sub -> y)))- | varToStr' cond == "notIn" = and [wrap (stripLocs' x) `notElem` map (wrap . stripLocs') (universe y) | x <- list x, y <- list y]- | varToStr' cond == "notEq" = not (x `eqNoLoc'` y)+ | varToStr' cond == "notIn" = and [extendInstances' (stripLocs' x) `notElem` map (extendInstances' . stripLocs') (universe y) | x <- list x, y <- list y]+ | varToStr' cond == "notEq" = not (x `astEq'` y) bool x | varToStr' x == "noTypeCheck" = True bool x | varToStr' x == "noQuickCheck" = True bool x = error $ "Hint.Match.checkSide', unknown side condition: " ++ unsafePrettyPrint x@@ -213,8 +217,8 @@ f x = x -- Does the result look very much like the declaration?-checkDefine' :: LHsDecl GhcPs -> Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool-checkDefine' x Nothing y = declName x /= Just (varToStr' (transformBi unqual' $ head $ fromApps' y))+checkDefine' :: String -> Maybe (Int, LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool+checkDefine' declName Nothing y = declName /= varToStr' (transformBi unqual' $ head $ fromApps' y) checkDefine' _ _ _ = True ---------------------------------------------------------------------
src/Hint/Monad.hs view
@@ -39,6 +39,8 @@ folder f a xs = foldM f a xs >>= \_ -> return () -- foldM_ f a xs yes = mapM async ds >>= mapM wait >> return () -- mapM async ds >>= mapM_ wait main = "wait" ~> do f a $ sleep 10+main = print do 17 + 25+main = print do 17 -- 17 main = f $ do g a $ sleep 10 -- g a $ sleep 10 main = do f a $ sleep 10 -- f a $ sleep 10 main = do foo x; return 3; bar z -- do foo x; bar z@@ -97,8 +99,10 @@ seenVoid wrap x = monadNoResult (fromMaybe "" decl) wrap x ++ [warn' "Redundant void" (wrap x) x [] | returnsUnit x] -- Sometimes people write 'a * do a + b', to avoid brackets.+-- or using BlockArguments they can write 'a do a b' doOperator :: (Eq a, Num a) => Maybe (a, LHsExpr GhcPs) -> LHsExpr GhcPs -> Bool doOperator (Just (2, LL _ (OpApp _ _ op _ ))) (LL _ OpApp {}) | not $ isDol' op = True+doOperator (Just (1, LL _ HsApp{})) b | not $ isAtom' b = True doOperator _ _ = False returnsUnit :: LHsExpr GhcPs -> Bool
src/Hint/Restrict.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeFamilies #-} @@ -18,6 +20,7 @@ import Config.Type import Data.Generics.Uniplate.Operations+import qualified Data.Set as Set import qualified Data.Map as Map import Data.List import Data.Maybe@@ -54,19 +57,20 @@ data RestrictItem = RestrictItem {riAs :: [String] ,riWithin :: [(String, String)]+ ,riBadIdents :: [String] ,riMessage :: Maybe String } instance Semigroup RestrictItem where- RestrictItem x1 x2 x3 <> RestrictItem y1 y2 y3 = RestrictItem (x1<>y1) (x2<>y2) (x3<>y3)+ RestrictItem x1 x2 x3 x4 <> RestrictItem y1 y2 y3 y4 = RestrictItem (x1<>y1) (x2<>y2) (x3<>y3) (x4<>y4) instance Monoid RestrictItem where- mempty = RestrictItem [] [] Nothing+ mempty = RestrictItem [] [] [] Nothing mappend = (<>) restrictions :: [Setting] -> Map.Map RestrictType (Bool, Map.Map String RestrictItem) restrictions settings = Map.map f $ Map.fromListWith (++) [(restrictType x, [x]) | SettingRestrict x <- settings] where f rs = (all restrictDefault rs- ,Map.fromListWith (<>) [(s, RestrictItem restrictAs restrictWithin restrictMessage) | Restrict{..} <- rs, s <- restrictName])+ ,Map.fromListWith (<>) [(s, RestrictItem restrictAs restrictWithin restrictBadIdents restrictMessage) | Restrict{..} <- rs, s <- restrictName]) ideaMessage :: Maybe String -> Idea -> Idea@@ -105,22 +109,48 @@ checkImports :: String -> [LImportDecl GhcPs] -> (Bool, Map.Map String RestrictItem) -> [Idea] checkImports modu imp (def, mp) =- [ ideaMessage riMessage $ if not allowImport- then ideaNoTo $ warn' "Avoid restricted module" i i []- else warn' "Avoid restricted qualification" i (noLoc $ (unLoc i){ ideclAs=noLoc . mkModuleName <$> listToMaybe riAs} :: Located (ImportDecl GhcPs)) []+ [ ideaMessage riMessage+ $ if | not allowImport -> ideaNoTo $ warn' "Avoid restricted module" i i []+ | not allowIdent -> ideaNoTo $ warn' "Avoid restricted identifiers" i i []+ | not allowQual -> warn' "Avoid restricted qualification" i (noLoc $ (unLoc i){ ideclAs=noLoc . mkModuleName <$> listToMaybe riAs} :: Located (ImportDecl GhcPs)) []+ | otherwise -> error "checkImports: unexpected case" | i@(LL _ ImportDecl {..}) <- imp- , let ri@RestrictItem{..} = Map.findWithDefault (RestrictItem [] [("","") | def] Nothing) (moduleNameString (unLoc ideclName)) mp+ , let ri@RestrictItem{..} = Map.findWithDefault (RestrictItem [] [("","") | def] [] Nothing) (moduleNameString (unLoc ideclName)) mp , let allowImport = within modu "" ri+ , let allowIdent = Set.disjoint+ (Set.fromList riBadIdents)+ (Set.fromList (maybe [] (\(b, lxs) -> if b then [] else concatMap (importListToIdents . unLoc) (unLoc lxs)) ideclHiding)) , let allowQual = maybe True (\x -> null riAs || moduleNameString (unLoc x) `elem` riAs) ideclAs- , not allowImport || not allowQual+ , not allowImport || not allowQual || not allowIdent ] +importListToIdents :: IE GhcPs -> [String]+importListToIdents =+ catMaybes .+ \case (IEVar _ n) -> [fromName n]+ (IEThingAbs _ n) -> [fromName n]+ (IEThingAll _ n) -> [fromName n]+ (IEThingWith _ n _ ns _) -> fromName n : map fromName ns+ _ -> []+ where+ fromName :: LIEWrappedName (IdP GhcPs) -> Maybe String+ fromName wrapped = case unLoc wrapped of+ IEName n -> fromId (unLoc n)+ IEPattern n -> ("pattern " ++) <$> fromId (unLoc n)+ IEType n -> ("type " ++) <$> fromId (unLoc n)++ fromId :: IdP GhcPs -> Maybe String+ fromId (Unqual n) = Just $ occNameString n+ fromId (Qual _ n) = Just $ occNameString n+ fromId (Orig _ n) = Just $ occNameString n+ fromId (Exact _) = Nothing+ checkFunctions :: String -> [LHsDecl GhcPs] -> (Bool, Map.Map String RestrictItem) -> [Idea] checkFunctions modu decls (def, mp) = [ (ideaMessage riMessage $ ideaNoTo $ warn' "Avoid restricted function" x x []){ideaDecl = [dname]} | d <- decls , let dname = fromMaybe "" (declName d) , x <- universeBi d :: [Located RdrName]- , let ri@RestrictItem{..} = Map.findWithDefault (RestrictItem [] [("","") | def] Nothing) (occNameString (rdrNameOcc (unLoc x))) mp+ , let ri@RestrictItem{..} = Map.findWithDefault (RestrictItem [] [("","") | def] [] Nothing) (occNameString (rdrNameOcc (unLoc x))) mp , not $ within modu dname ri ]
src/Util.hs view
@@ -82,10 +82,8 @@ configExtensions :: [Extension] configExtensions = [e | e@EnableExtension{} <- knownExtensions] \\ map EnableExtension reallyBadExtensions -badExtensions =+badExtensions = reallyBadExtensions ++ [Arrows -- steals proc- ,TransformListComp -- steals the group keyword- ,XmlSyntax, RegularPatterns -- steals a-b ,UnboxedTuples, UnboxedSums -- breaks (#) lens operator ,QuasiQuotes -- breaks [x| ...], making whitespace free list comps break ,DoRec, RecursiveDo -- breaks rec@@ -94,4 +92,5 @@ reallyBadExtensions = [TransformListComp -- steals the group keyword+ ,XmlSyntax, RegularPatterns -- steals a-b and < operators ]